-
Notifications
You must be signed in to change notification settings - Fork 57
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
XLS-13d: Tickets & Ticket Batching #16
Comments
Thank you for the very very thorough & well thought through write up. Especially the multi sign use case is very familiar and I'd love to implement & use this. |
I thought I'd just repost the below as a much simpler solution which covers many of the usecases
|
And also a follow on comment for a slightly different usecase:
|
Technically speaking I think we could safely use the old |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Tickets
Tickets are a proposed change to the XRP Ledger protocol. They would allow selected transactions on a single account to be processed out of order, which could benefit some multisigning situations. This document outlines the use model and describes the current implementation.
The Problem Statement
Multisigning on the XRP Ledger. Cool, huh? And it's actually getting used. In March of 2018 approximately 4.4% of all payment transactions on the XRP ledger were multisigned. That's over 33,000 payments in that single month. That's not a large percentage, but when you consider the total number of multisigned payments, it's significant.
As long as multisigning is being used in automated situations, it's great. But for non-automated situations there's at least one downside.
Sequence Numbers
The trick is that in the XRP Ledger...
For example, somebody just created an account for Zoe by sending her some XRP. Zoe's brand new account has a starting sequence number. The very first transaction that Zoe applies to her new account must contain that sequence number. If her first transaction omits the sequence number then the transaction fails. If her first transaction uses a sequence number other than the one in her account, then the transaction fails.
Once Zoe submits that first successful transaction then the sequence number on the account increments. So Zoe's next transaction must contain a sequence number one greater than the sequence number in her previous transaction. The story proceeds from there.
What does this have to do with multisigning? It impacts two possible multisigning scenarios.
Multiple Independent Signers on One Account
With multisigning, it's possible to have multiple independent signers on a single account. Suppose Bonnie and Claudine meet at a Mixed Martial Arts club and decide to start a business together: C-Body Guards. They create an account for the business and set it up for multisigning. Bonnie and Claudine trust each other a great deal. They set up the account multisigning so Claudine can sign or Bonnie can sign for any transaction. (For the detail oriented, Bonnie has a signer weight of 1, Claudine has a signer weight of 1, and the signer list has a quorum of 1.)
Everything was going along just fine until one day when Claudine and Bonnie created independent transactions at about the same time. They both looked at the business account at about the same time and saw the same sequence number. They independently used that same sequence number to create two independent transactions. They submitted their transactions at about the same time. Just by chance Bonnie's transaction made it into the ledger first, so Claudine's transaction failed with
tefPAST_SEQ
. Claudine was puzzled by the error but re-submitted her transaction, with an updated sequence number, and the second one went through just fine.Eventually Bonnie and Claudine figured out what had happened.
If Bonnie and Claudine started creating transactions frequently, then the rate of collisions would go up. Eventually it would become a problem.
Multiple Required Human Signers
Prince, Elvis, and Moby decide to go into business together selling tree house kits. However Prince, Elvis, and Moby don't trust each other very much. So when they create their business account they decide that all three of them must sign for any transaction. (For the detail oriented, Prince, Elvis, and Moby each have a signer weight of 1, and the signer list has a quorum of 3).
Elvis decides to throw a party in honor of creating the business and creates a transaction to buy supplies for 2000 peanut butter, banana, and bacon sandwiches. Elvis signs the transaction, using the current account sequence number, and then sends it to Prince and Moby to sign. Moby sits on the transaction because he's not convinced that peanut butter, banana, and bacon sandwiches are the best party food.
In the mean time Prince decides that they should get down to business and buy some lumber for their tree house kits. But Prince has a conundrum. If he creates his transaction with the sequence number one greater than the account sequence, then he must wait until Moby has signed Elvis's transaction and Elvis's transaction has been submitted to and validated by the network. If Prince creates his transaction with the current account sequence number, and if it is validated by the network before Elvis's transaction, then Elvis's transaction could never be validated since it would have a sequence number that is already used. As a consequence Moby and Elvis might need to renegotiate; that would be a pain.
It would be great if there were a way to create transactions so acceptance of a later transaction would not be blocked by acceptance of an earlier transaction.
Enter Tickets
The proposed solution to this problem (one transaction blocking another due to sequence number ordering) is a new ledger type called a Ticket. Fundamentally, a Ticket can be created on an account, then later that Ticket may be used in place of a sequence number. The order in which Tickets are used is not constrained by sequence number ordering.
So Bonnie, Claudine, Prince, Elvis, and Moby could all solve their problems by creating a bunch of Tickets on their accounts first. Those Tickets would then be assigned to individuals. The individuals can then create their transactions, using Tickets instead of sequence numbers, without having to worry about the sequence number ordering of their own, or anybody else's, transactions.
Now that you understand the point of Tickets, you may want to read about the details, which follow. If you just want the summary, and not the details, you can skip to the Summary and the FAQ sections toward the bottom of this document.
Discussion of the Details
Creating a replacement for something as fundamental as sequence numbers in transactions is delicate work. The following design is as simple as possible. But even with that, there are a lot of details to attend to.
Why Sequence Numbers?
We need to start by understanding why each account has a sequence number in the first place.
The most important role of the
Sequence
number is that it makes each transaction on an account unique. Consider if Zoe created a transaction to pay 10,000 XRP to Brent. Once that transaction is validated in the ledger what prevents that transaction from being arbitrarily replayed time and again? All of the data in the transaction is correct and the transaction is correctly signed. Otherwise the transaction would not have been validated in the first place. If a transaction is valid, what is it that makes that transaction only valid one time?The rule that a
Sequence
number may not be re-used in two or more transactions is what keeps Zoe's transaction from being reused. So, because of thatSequence
number, Zoe can have confidence that her transaction will only be validated once.Additionally, the
Sequence
number plays a role in guaranteeing that each validated transaction in the ledger has a unique hash. The uniqueness of the hash of each validated transaction is important because all transactions are located in the ledger by their hashes. The ledger is not set up to handle hash collisions because hash collisions are not expected to ever occur. The hash of each transaction is, in part, guaranteed to be unique because the serialized data in each transaction (which includes theSequence
number) is guaranteed to be unique.What matters for both of these considerations is that the
Sequence
number must be unique for each transaction on the account. They don't really need to be sequential. But there's a lot of complexity that is reduced by requiring eachSequence
number to be consecutive. If they are consecutive then they will automatically be unique (until the integer overflows). And consecutively produced and consumedSequence
numbers are easy to think about and compute.So whatever we do with
Ticket
s needs to carefully:Sequence
numbers provide whileCreating
Ticket
sCreating One
Ticket
In order to use a
Ticket
on an account, thatTicket
must first be created. That takes a transaction.A successful
TicketCreate
transaction adds aTicket
in the directory of the owning account. ThatTicket
contains aTicketSequence
which is now available to be used by a future transaction on that account. When theTicket
is created it computes a newTicketSequence
number based on theSequence
of the account root. ThatTicketSequence
number will play an important role when it is time to consume theTicket
.A unique
Ticket
can be identified in the ledger with only two pieces of information: the ID of the account that owns theTicket
and theTicketSequence
number of thatTicket
.Once created, a
Ticket
lives in the owning account's directory. Therefore, adding thatTicket
to the account takes one standard XRP reserve increment on that account (5 XRP in January of 2020). The moreTicket
s held by the account, proportionally more reserve is required on the account. The reserve is returned to the account when theTicket
is consumed (which removes theTicket
from both the account directory and from the ledger).The
TicketSequence
ValueWhat is the integer value of the
TicketSequence
associated with eachTicket
? Uniqueness is an important consideration here. So how do we compute the value of aTicketSequence
? There's really one rule, but exactly how that rule is applied depends on how theTicket
is created.That rule may not be very clear simply from reading the description. Let's look at the two ways of creating
Ticket
s.Creating a
Ticket
with aSequence
If a
TicketCreate
transaction uses aSequence
number then theTicketSequence
of the createdTicket
is one greater than the transaction'sSequence
number.Say Alice sends a
TicketCreate
transaction withSequence
7. If that transaction is successful, then theTicket
it creates will haveTicketSequence
8. That's because the successful transaction'sSequence
, by design, equals Alice's account rootSequence
. Since Alice's account rootSequence
is 7, and we're using upSequence
7 with the current transaction, then the next viableSequence
is 8. And, since the largestTicketSequence
created was 8, then the account rootSequence
moves to 9.By moving Alice's account root
Sequence
to 9, we make sure that Alice will not have any valid transactions where theSequence
and theTicketSequence
values are the same. Later we'll see why that's useful (see Offers with Ticket).It's worth noting, however, that Alice's account root
Sequence
just jumped from 7 to 9. So if Alice tries to send a transaction withSequence
8 she'll get atefPAST_SEQ
error.One valid way to think of this is that creating a
Ticket
reserves the correspondingSequence
number value so it can be used later. We'll be using it out of order, so we're calling it aTicketSequence
, but the value is reserved.Creating a
Ticket
with aTicketSequence
We're getting a bit ahead of ourselves, but once a
Ticket
has been created, thatTicket
can be used to create anotherTicket
. The calculation for theTicketSequence
value of the newTicket
works a bit differently than with aSequence
. Here's the rule:If a
TicketCreate
transaction uses aTicketSequence
number, then theTicketSequence
of the newly createdTicket
is the account root'sSequence
number.Let's pick up from where Alice left off. Alice has a
Ticket
withTicketSequence
8. Alice's account rootSequence
is 9. Alice now does aTicketCreate
withTicketSequence
8. Since Alice's account rootSequence
is 9, and theTicketCreate
is not usingSequence
9, then the new Ticket getsTicketSequence
9. Alice's account rootSequence
moves to 10.Even though the steps are a little different, we again see that creating the
Ticket
tucks away the next available unused and unreserved account rootSequence
so Alice can use it later.Creating Lots of
Ticket
sThere are problems with creating only one
Ticket
at a time on an account with multiple required human signers. For example, it doubles the work. You must create a transaction to create aTicket
. Then you must create the originally intended transaction and apply theTicket
. And all of those transactions must be signed by all of the required signers.In effect
Ticket
s have doubled the amount of work we must do. If that were the state of the art, thenTicket
s would not be worth the effort.So we allow one
TicketCreate
transaction to create a whole bunch ofTicket
s in one fell swoop. There is still the hassle of making a transaction and getting it signed by all of the multisigners. But only one transaction needs to be signed and submitted to create up to 250 Tickets. This seriously reduces, but does not eliminate, the hassle factor.The XRP Ledger sets a limit on the amount of metadata one transaction is allowed to create. The question came up, how close is a transaction that creates 250 Tickets to that limit? Given an initial implementation of
Ticket
s, it was found that 5,041 Tickets could be created in a single transaction without excessive metadata. So setting the limit at 250 leaves us over an order of magnitude away from the boundary. That seems like enough safety margin.Batch
TicketSequence
AssignmentSo when a batch of
Ticket
s is created, how are theTicketSequence
values assigned?All of the
Ticket
s created in the same batch have contiguous integral values. The first value is determined as described in Creating OneTicket
. Once the value of that firstTicket
is determined then each subsequentTicket
in the batch increments that starting value.So, returning to Alice, Alice's account root
Sequence
is now at 10. She's tired of making just oneTicket
at a time, and she has enough XRP that she can afford the reserve for an additional 100Ticket
s. So she sends aTicketCreate
transaction with aTicketCount
of 100 andSequence
10. That will create a batch ofTicket
s for her account that haveTicketSequence
values 11 through 110. Alice's account rootSequence
will have the value 111 when the transaction completes.Using a
Ticket
Sequence
numbers are heavily used throughout the XRP Ledger for several different purposes. So it's worth thinking hard about how using aTicketSequence
in place of aSequence
number will effect transactions and the ledger.There are four primary ways in which
Sequence
numbers affect the ledger and transaction processing:Sequence
number to verify that the transaction is unique in the ledger and that the hash of the validated transaction is unique.Sequence
number as a persistent identifier in a ledger object. This happens both...Offer
s andCheck
s) andEscrow
andPayChan
).Sequence
number of a transaction is used to sort transactions. This is important becauseTxQ
) stores queued transactions in that order.Sequence
number of a transaction can be used to identify all of the validated transactions associated with an account, as well as the order of those transactions.All of these behaviors will be affected by the presence of
Ticket
s in transactions.Transaction Validity With Tickets
Unique Transaction Hash
An important purpose that
Sequence
numbers fill is guaranteeing the uniqueness of the hash of validated transactions. How is that requirement met byTicket
s?First let's compare a simple transaction using a
Sequence
number to the same transaction using aTicket
. With aSequence
number we see:Now, for contrast, suppose instead that we'd created a
Ticket
usingTicketSequence
number 12. We'll now use thatTicket
.By examining the differences between these two transactions you can see that they will generate different hashes:
Sequence
field is present in both transactions, but in one transaction it's non-zero, in the other it's zero.TicketSequence
field is new in the transaction that uses theTicket
.The
Sequence
field is always required, but if aTicketSequence
is present theSequence
field must be zero. If theSequence
field is ever omitted then the transaction is malformed. Correspondingly, a transaction that includes both a non-zeroSequence
number and aTicketSequence
field is treated as malformed and returns atem
code.Testing for Transaction Validity and Uniqueness
One of the phases of transaction validity and uniqueness testing is
Sequence
validation. Usually theSequence
in the transaction is compared to theSequence
in the account root of the transaction'sAccount
. If they are equal then that part of the validity check is good.But here we have a
Sequence
of zero. First it's worth noting that when an account root is created it always gets aSequence
greater than zero. Since the account rootSequence
field is incremented, it can never validly have a value of zero. Therefore there should never be any confusion about a transaction with aSequence
of zero. If theSequence
is zero in a transaction, then there had better be aTicketSequence
. (Sequence
field rollover could be a possibility a few years out from now. Before it becomes an issue there will be a different ledger feature to preventSequence
rollover from happening before the Sun goes Red Giant.)If the
Sequence
is zero then, in order to be a valid transaction, the presence of theTicket
in the ledger must be verified during the preclaim phase of transaction validation. If theTicket
is not found in the local ledger then the transaction is not forwarded to the network. Instead, the transaction is queued locally to be retried on the next ledger. The transaction will not be forwarded to the network until the appropriateTicket
is found in the local ledger.Ledger Artifacts
Every transaction that operates on an
Account
today leaves some kind of artifact in the ledger. (We're ignoring pseudo transactions, since they don't operate on actualAccounts
). Today, at a minimum, every transaction on anAccount
causes theSequence
of that account root to increment. WithTicket
s we lose that guarantee of theSequence
increment. But there are still guaranteed changes to the ledger, including to the account root.Ticket
is used, then the consumedTicket
is removed from the ledger. So theTicket
itself will be removed from the ledger.Ticket
removed, so the directory will change as well.PreviousTxnID
field in the account root will be modified to contain the hash of the transaction that consumed theTicket
.OwnerCount
field in the account root will decrease, but that is not required. If the transaction adds one entry to the account's directory, then the removedTicket
will be counter-balanced by the added directory entry (say anOffer
) which could cause theOwnerCount
field to be left unchanged.Balance
field would change due to the fee, but this too is not required. If the transaction does not require a fee (for example, a Key Reset Transaction) then no fee will be consumed. Or, say with aCheckCash
transaction, the transaction could bring just enough XRP into the account to counterbalance the fee. If this were to occur, then theBalance
would not change.Ledger Artifacts With Explicit
Sequence
NumbersSome transactions produce ledger artifacts that explicitly include the sequence number of the transaction. Specifically, those are
Offer
andCheck
.Offer
s withTicket
Offer
s are the classic ledger type that incorporates aSequence
field. TheSequence
of the transaction is also used to build the index of theOffer
in the ledger.If we create an
Offer
using aTicket
instead of aSequence
what are the consequences? Well, we certainly can't use theSequence
to build theOffer
; theSequence
is zero. We'll need to use theTicketSequence
number.If we do this will we need to be concerned about creating
Offer
s in the ledger with duplicate ledger indexes? Fortunately no. If you read the earlier description for howTicketSequence
values are computed you'll see that there is no overlap between validSequence
values and validTicketSequence
values for a given account.Is there any way that an XRP Ledger user can receive an advantage by creating an
Offer
with aSequence
number that is earlier than anOffer
they created before? First, how could that happen?Ticket
withTicketSequence
3.Offer
withSequence
4.Offer
using theTicket
she previously created. ThatTicket
hasTicketSequence
3. So this newestOffer
has aSequence
of 3, which came from theTicket
.In this example Anne's most recently created
Offer
has aSequence
of 3, which precedes an olderOffer
withSequence
4.Now that we understand how it can happen, is there any advantage to be gained from it? As far as I can tell, no. An
Offer
has value based on where it is located in its Book. Each Book for a currency pair is separated into sections. Each section of that Book containsOffer
s of exactly the same quality. When a newOffer
is added to the Book, thatOffer
is appended to the otherOffer
s in that section. So, even though theOffer
has an earlierSequence
number, the position of theOffer
in the Book is unaffected.Check
s withTicket
The same thought process we followed for
Offer
s withTicket
s applies toCheck
s withTicket
s. Building aCheck
using aTicketSequence
number in place of aSequence
number cannot lead to duplicate ledger indices forCheck
s. And there is absolutely no advantage to be gained by creatingCheck
s out ofSequence
order.Ledger Artifacts With Implicit
Sequence
NumbersThere are also two ledger artifacts that use the
Sequence
when constructing their ledger indices, but they do not store theSequence
number in the ledger object. Those two ledger objects areEscrow
andPayChan
. These two ledger types have the same concerns asOffer
s in terms of uniqueness. The answer to uniqueness concerns is also the same as forOffer
s. Since there is no overlap between an account'sSequence
andTicketSequence
values, the transaction'sTicketSequence
could not have been used to previously create anEscrow
or aPayChan
. So using theTicketSequence
number in place of theSequence
for theEscrow
orPayChan
cannot lead to duplicate ledger indices.Transaction Ordering
There are two places where unfinished transactions are sorted. They are:
TxQ
) sorts its queued transactions in order based on a number of considerations. Those considerations include the transaction fee, the account number, and theSequence
number of the transaction.The ordering of transactions might be a place where someone gains advantage by moving one transaction in front of another. So we'll need to think about the rules for sequencing transactions that contain
TicketSequence
s interspersed with transactions that containSequence
numbers.Application to the Ledger
Currently transactions are applied to the ledger in a sorted order (for the detail oriented, the
CanonicalTxSet
does this sorting). This sort order is applied before transactions are actually accepted into the ledger. So the sorting must allow for the possibility of two transactions having the sameSequence
number or the sameTicketSequence
. If that occurs then only one transaction with a givenSequence
number orTicketSequence
is actually accepted into the ledger. But since we are dealing with transactions that are not yet validated, we must be ready for these kinds of things to occur.The sort order is currently determined by comparing two transactions this way:
Sequence
number goes in front. If theSequence
numbers are equal, then...This comparison methodology is insufficient once we can submit a transaction using a
Ticket
. For one thing, since theSequence
for a transaction with aTicket
is zero, transactions withTicket
s would always be sorted to the front and then sorted by transaction ID.There are a variety of sort orders which take
TicketSequence
into account that could be used. The one we ended up with sorts transactions withTickets
behind all transactions withSequence
numbers for a given account. How we ended up with this sort order is mostly a historical accident. But it's an easy sort order to understand and it is working well. So there's no motivation to change the sort order at this point.Here's the transaction sorting order with
TicketSequences
taken into account:Sequence
number goes in front. ZeroSequence
numbers go to the back. If theSequence
numbers are equal, then...TicketSequence
s then theTicketSequence
with the smaller number goes to the front. If either transactionhas no
TicketSequence
or if theTicketSequence
numbers are the same, then...TxQ
Transaction OrderingThe
TxQ
also keeps ordered lists of transactions. Actually, it keeps two distinct kinds of lists. One list is of all transactions byFeeLevel
. (FeeLevel
is similar to the ratio of the actual fee paid to the minimum fee that would allow the transaction into a non-busy ledger.) This particular list ignores account IDs andSequence
numbers. SoTicket
s will have very little impact in this list.However the
TxQ
keeps another kind of list as well. For everyAccountID
stored in theTxQ
there's a sorted list of the transactions queued for that account. These lists are currently sorted bySequence
. So withTicket
s these lists need the same sort order as theCanonicalTxSet
.Possible Ordering Consequences
Both of these sort orders (
TxQ
andCanonicalTxSet
) leave open the possibility that games could be played by doing a late insertion of a transaction in front of a transaction that was already posted (but is not yet in a validated ledger).Here are two different ways this reordering could happen:
Ticket
s:Ticket
s on her account.TicketSequence
.TicketSequence
.Sequence
numbers andTicket
s:Ticket
s on her account.TicketSequence
.Sequence
number.Sequence
number) will be sorted in front of the earlier transaction (with theTicketSequence
).I'm not convinced that we need to worry about this kind of reordering.
Ticket
(s).TxQ
already allows the account owner to replace one transaction with another by re-issuing a transaction with the same sequence but a higher fee. To the best of our current knowledge this is not being used to manipulate the ledger.Someone who's more clever with possible ledger manipulations may have ideas for how this kind of reordering could be abused. If so I'm all ears.
Identifying Transactions That Can Never Complete
Both of these queuing mechanisms, the
TxQ
andCanonicalTxSet
, want to solve the problem of identifying transactions that can never succeed. Any transaction that can never succeed should simply be thrown out rather than re-queued. Is there any way to accomplish this withTickets
?Fortunately, yes. By looking at the
Sequence
field of the account root we can determine if a transaction with aTicket
can never succeed.Ticket
s are created inSequence
order, but with potential gaps. This means that:Ticket
andSequence
is past theTicketSequence
number, andTicket
is not in the ledger, thenTicket
was created and already consumed orTicket
never was (and can never be) created.Ticket
can never become available for this transaction, so the transaction can never succeed.So the rule is just a bit more complicated than the rule we currently have with
Sequence
numbers.Similarly, if a transaction uses a
TicketSequence
with a number that is equal to or higher than the account rootSequence
field, then it is a transaction from the future. We can retry that transaction a few times (on upcoming ledgers) to see if thatTicket
ever gets created. But the retry will be local to the machine where the transaction was submitted. The transaction will not be forwarded to the network unless theTicket
is actually seen in the local ledger.Ticket
s andtec
ErrorsA transaction that consumes a
Ticket
has some interesting new behaviors if the validated transaction generates atec
error code.Usually a transaction that generates a
tec
has small but important interactions with the ledger:Sequence
number is advanced by 1.There is one
tec
code (tecOVERSIZE
) that has more impact on the ledger; it also removes a collection of unfunded and expired offers. But, under ordinary circumstances, a transaction that generates atec
code does not change the ledger very much.Note that one of the things a
tec
code always does is advance the account rootSequence
number. However, if the transaction generating thetec
is also consuming aTicket
we can't follow that path. Instead we remove theTicket
that was in the transaction from the ledger. The account rootSequence
does not change.This is not a big change, but it's worth drawing people's attention to the change so they can think about it.
Ticket
s and theAccountTxnID
FieldThe
AccountTxnID
field allows you to chain your transactions together as documented here: https://xrpl.org/transaction-common-fields.html#accounttxnid. A transaction that specifies anAccountTxnID
field is only valid if the specified transaction ID matches the transaction that was last sent by the account.Ticket
s on the other hand are intended to remove restrictions on transaction ordering.Combining these two features makes it very difficult for the TxQ to predict whether a transaction is likely to succeed.
Therefore the initial implementation for
Ticket
s does not allow a transaction that uses aTicketSequence
to also include anAccountTxnID
field. Such a transaction is rejected withtemINVALID
. If we find a crying need for such a feature in the future, then we can figure out how to implement it well.The Transaction Database and Indices
One of the SQLite databases that rippled keeps is called the Transaction database. As you might expect, that database stores transactions in a table. It also keeps several indices that make it easier to locate transactions in that database. The Transaction database looks like this:
The transaction's
Sequence
number shows up in the top-mostTransactions
table as theFromSeq
field. But it makes no additional showing in any of the other tables or indexes. Note that it's easy to get confused by theTxnSeq
fields in the other tables and indices. But theTxnSeq
field is not theSequence
number that is embedded in the transaction.TxnSeq
carries the order of transaction application within one ledger – a much different thing.We spent some time examining these tables and came to the conclusion that they do not need any modifications to take
Ticket
s into account.The strongest evidence that the
Ticket
would need to be present in the tables or indices would be if the transactionSequence
field (FromSeq
) had a strong present there. AlthoughFromSeq
is present at the top-most level of the Transactions table, none of the other indices referenceFromSeq
.There are currently two standard methodologies to extract the transactions associated with an account from the
Transactions
database table. Neither of those methodologies rely on the transactionSequence
. The methodologies are:AccountTransactions
table. The technique is to examine consecutive ledgers byLedgerSeq
looking forAccount
and saving the correspondingTransID
. Those results are then placed in order first byLedgerSeq
and then byTxnSeq
.TxnSeq
is the order in which transactions were placed in the ledger, not theSequence
number in the transaction. The order in which all transactions were applied to a ledger must also be the order in which any given account's transactions were applied.PreviousTxnID
field of the account root. By using thePreviousTxnID
field and walking backwards through transactions using thetx
RPC command you can request the last transaction that modified the account. By looking in the metadata of that transaction you can locate the previous value of thePreviousTxnID
field. You can follow this chain until you run out of history or the account root is created.Since neither of these methodologies rely on the transaction's
Sequence
number, they won't be made more efficient by addingTicket
information to the database. We have also worked with the Data team to see if they have any dependency on theFromSeq
field (which might indicate a need forTicketSequence
information in the table). No dependencies onFromSeq
were identified by the Data team.Roads Not Taken
The Tickets proposal has been around for a while. Here are some additional features that have been considered and are not part of the current implementation.
Ticket Targets
The original Tickets proposal allowed an account to create a
Ticket
with aTarget
. TheTarget
would be an account ID that is different from the account creating theTicket
. ATicket
with aTarget
could be consumed by either the account that created theTicket
or by theTarget
account.The goal was to have one account that would only be used to produce
Ticket
s for an organization. Then thoseTicket
s could be assigned, usingTarget
, to whatever accounts needed them.However consider this scenario:
Ticket
with Cap's account as theTarget
. Bucky's account is atSequence
5, so theTicket
is assigned 5.Sequence
5. Cap creates an Offer on his account usingSequence
5. Cap's offer goes in the ledger.Ticket
is available.Ticket
to create anotherOffer
. This newOffer
also hasSequence
5, since that is the number on theTicket
.Offer
s in the ledger with identical ledger indices: theOffer
he made with theSequence
and theOffer
he made with theTicket
.If including the
Target
were really important there would probably be ways of working around this difficulty. But any solution would impact the way thatOffer
,Check
,Escrow
, andPayChan
ledger indices are calculated. That would be a very invasive change. We should think very hard before deciding to make that kind of change to the ledger.Ticket Expiration
When
Ticket
s hadTarget
s, it was thought that it might be useful to give aTicket
an optionalExpiration
, similar toOffer
s. However, at the moment, aTicket
can only be used by the account owning that particularTicket
. So there seems to be very little value in giving anExpiration
to aTicket
.Ticket Cancel Transactions
When
Ticket
s had anExpiration
field it was thought that aTicketCancel
transaction would be necessary. Otherwise how could an expiredTicket
be removed from the ledger? However sinceTicket
s, in this proposal, do not expire thenTicketCancel
transactions no longer fill a useful role. SoTicketCancel
is no longer proposed. If aTicket
needs to be removed from the ledger it can be consumed by any transaction on its account like, for example, a noop. See https://xrpl.org/about-canceling-a-transaction.html.Special Handling for Account Root Sequence Jumps
One of the features of the XRP Ledger is that the full transaction history of an account can be reconstructed. Prior to
Ticket
s if you found the transactions for everySequence
number on an account, then you know you have every transaction and you also know the order in which those transactions were applied to the ledger. WithTicket
s things get more complicated.The transaction metadata still contains the full story. But you can't simply look at the
Sequence
number.Sequence
number does not change between two transactions, then the metadata will show aTicket
node being deleted.Sequence
number leaps between transactions, then the metadata will show as manyTicket
s being created as the size of theSequence
number leap.If folks decide that digging around in metadata looking for
Ticket
s being created or deleted is too messy we could choose to add artifacts to the account root to annotate the change. Such account root additions would be purely to help external tools understand chains of transactions.For a straw man, let's propose that we add two new optional fields in the account root:
SubSequence
, andJumpSequence
.SubSequence
would work as follows:SubSequence
, it would be 32-bit unsigned.Sequence
number, we remove theSubSequence
field, if present.Ticket
:SubSequence
field exists in the account root, we create one and set it to 1.SubSequence
field exists in the account root, we increment it.This will cause the metadata in the account root of the transaction to indicate whether the prior transaction was a ticketed one or not. And it doesn't (in general) tend to make the ledger larger since the
SubSequence
field is removed once we're done with it.The
SubSequence
field would be sufficient to deal with the presence ofTickets
in transactions. But it would not be sufficient to cover batch creation ofTickets
. We would need yet another account root field to indicate that theSequence
number had taken a leap. For that we could consider adding an optionalJumpSeq
field to the account root. Its rules would be:JumpSequence
, it would be 32-bit unsigned.TicketCreate
then we remove theJumpSequence
field if it is present.TicketCreate
transaction:JumpSequence
field exists in the account root, we create one and set it to the count.JumpSequence
field already exists in the account root, set it to the count.We have seen no evidence that tools outside the XRP Ledger need this kind of information. So the additional fields are not being added.
TicketSignerQuorum
As noted earlier, it requires the full complement of multisigners to construct a valid
TicketCreate
transaction. It has been suggested that this pain could be reduced by adding an optionalTicketSignerQuorum
field to theSignerList
.For those unfamiliar with multisigning in the XRP Ledger, an account is made multisigning by adding a
SignerList
to that account using theSignerListSet
transaction. EachSignerList
consists of one to eight signers, where each signer has aSignerWeight
and the entire list has aSignerQuorum
. For a transaction to be validly multisigned it does not need all signers, it only needs enough signers so that the sum of theirSignerWeight
s meets or exceeds theSignerQuorum
.By providing a
TicketSignerQuorum
, and having that value be smaller than the regularSignerQuorum
, the creator of theSignerList
would allow aTicketCreate
transaction to require fewer signers. In fact, they could meet theTicket
creation quorum with a single (multisigned) signature if they set theTicketSignerQuorum
to one. ASignerListSet
transaction which set theTicketSignerQuorum
larger than theSignerQuorum
would be malformed.The
TicketSignerQuorum
value would be a one-trick pony. The only time theTicketSignerQuorum
would be checked would be onTicketCreate
transactions. In all other cases the regularSignerQuorum
would be checked.This feature has not been added. Once
Ticket
s are in active use on the network we may find that this would be a desirable feature. If so, we can add it at that later time.New Attack Vectors
The point of
Ticket
s is to allow execution of transactions out of order. That, in turn opens up some vulnerabilities in the ledger. Here's what has been identified so far:Submit Order is not Canonical
Prior to
Ticket
s all transactions on a single account had to be submitted to the ledger in canonical order. WithTicket
s you can submit a transaction withTicketSequence
5 followed byTicketSequence
4. If they are submitted quickly enough they will probably both go into the same ledger. They will be applied to the ledger in canonical order (4 followed by 5). Suppose thatTicket
s 4 and 5 are already in the ledger and the following transactions are submitted:So the transactions work in submit order. The local rippled tries them in that order when the transactions are initially submitted to the network. Since the transactions both succeed in submit order, the transactions are forwarded to the full network.
The validators apply the transactions in canonical order. In canonical order the
SetRegularKey
is applied to the ledger first. So theAccountSet
transaction fails; it has the wrong signature. Since the signature is wrong we cannot charge the fee. So the entire network has processed a transaction that is guaranteed to fail and a fee is not charged.This is really a new version of a preexisting attack. A similar attack can be made by submitting two different transactions on the same account, and using the same
Sequence
number, but to two different rippled client handlers. Both of the transactions are valid, so the two client handlers will individually forward both transactions to the full network. One transaction will win and the other will fail with atefPAST_SEQ
. The one that fails is wasted work by the entire network.Tickets as Ledger Spam
There are two different kinds of ledger spam:
It is possible to argue that the account reserve is not always effective as a deterrent. Anyone who has a large amount of XRP that doesn't need to be liquid can tie up that liquidity in their reserve. They are not motivated to reduce that reserve until they need the liquidity. In effect they can view their reserve simply as a form of escrow.
This means a hostile or un-caring user with lots of XRP could create millions of
Ticket
s on their account. The entire network must pay the price for such volume in the ledger. So it makes sense to take preemptive steps to avoid this problem.Approaches to Managing Ticket Ledger Spam
Four different approaches to managing this possible ledger spam were explored:
Ticket
storage format so multipleTicket
s occupy less space in the ledger.Ticket
s that a singleTicketCreate
transaction can produce.Ticket
s.Ticket
s an account is allowed to keep in-ledger.An extended discussion occurred. The conclusion was to choose limiting the number of
Ticket
s any individual account root can hold.Limiting the Number of
Ticket
s an Account Can Keep In LedgerFor ticket count limiting to be effective, we need to make the number of
Ticket
s an account holds easy to discover. So we added an optional 32-bitTicketCount
field to the account root. That optionalTicketCount
must be maintained with every Ticket-based operation. With this field the account'sTicketCount
can easily be found by looking in a single central location.Once we have this
TicketCount
it becomes easy to set an arbitrary cap for the number ofTicket
s an account can keep in ledger. The cap only needs to be enforced forTicketCreate
transactions.The only remaining question is the behavior when a
TicketCreate
would exceed the maximum threshold. The behavior we picked was to start with a threshold of 250Ticket
s. If aTicketCreate
transaction would cause the number ofTicket
s held by an account to exceed that threshold, then theTicketCreate
fails with atecDIR_FULL
error. If the resultingTicketCount
is at or below the threshold, then all of the requestedTicket
s are created. This means, since the maximum number ofTicket
s oneTicketCreate
can produce is 250, that maximum number can only be created if the account in question has noTicket
s.Summary
To briefly summarize the current proposal, we have the following new rules.
Ticket
.TicketSequence
can be used in place of aSequence
number in a transaction.TicketSequence
must also include theSequence
field, but thatSequence
field must be set to zero.Ticket
s may be submitted to the network in any order; they are not order constrained like transactions withSequence
numbers.TicketSequence
and a non-zeroSequence
field is malformed.TicketCreate
transaction can create up to 250Ticket
s in a single transaction.Ticket
s that a single account can keep in the ledger is 250.TicketCreate
transaction that would take a given account above the limit of 250Ticket
s fails with atecDIR_FULL
.Ticket
can use theTicket
in a valid transaction.Ticket
on an account takes one standard reserve unit on that account.Ticket
is not forwarded to the network unless theTicket
is present in the local ledger.Ticket
s are sorted behind transactions with non-zeroSequence
numbers.TicketSequence
is applied to the ledger, with either atesSUCCESS
or atec
code, thatTicket
is removed from the ledger.FAQ
Q: Are
Ticket
s required for using the XRP Ledger?A: No.
Ticket
s address a specific use case where submitting transactions in a pre-determined order is inconvenient. If you don't have that use case you do not need to know aboutTicket
s.Q: How much does a
Ticket
cost?A: The fee for the transaction that creates one or more
Ticket
s is the same fee as for other standard transactions. So, for example, multisigning the transaction makes it more expensive than signing with the master key or a regular key. But there are no special or unusual costs associated with the transaction.In addition to the fee for the transaction, each
Ticket
held by an account increases that account's reserve. That reserve is correspondingly reduced when eachTicket
is consumed.Q: How many
Ticket
s can I create in a single transaction?A: From 1 to 250. The compute time required for a
CreateTicket
transaction to add 250Ticket
s to the ledger is slightly less that the compute time required by a single complicated (3 path) payment. So the compute time to create 250Ticket
s has a reasonable bound.Q: How many
Ticket
s can I keep in my account?A: Up to 250. There has been minor concern about the possibility of (virtually) unlimited
Ticket
s owned by an account as being a form of ledger spam. We are preemptively removing that possibility. If the 250 limit is found to be too restrictive in practical situations then the limit is easy to increase (albeit with an amendment).Q: Can I create a
Ticket
on one account and use it in a different account?A. No. The
TicketSequence
number that eachTicket
captures must be one that is associated with the account that creates theTicket
. See the Ticket Targets discussion.Q: How do I cancel a
Ticket
?A:
Ticket
s cannot be canceled, they must be consumed to be removed. One way to do that would be to associate aTicket
with aNOOP
transaction (anAccountSet
that changes nothing). Another way to do it would be to use aTicket
in a transaction that will generate atec
error code.Since
Ticket
s don't expire we don't foresee a need for an account to remove large numbers ofTicket
s.Q: How can I get a list of the
Ticket
s on my account?A: The
account_objects
RPC command can be used to list all objects held by an account. Specify"type": "ticket"
to get only the un-consumed Tickets.Q: What happens if I use the same
Ticket
in two different transactions?A: Assuming that the
Ticket
is valid, at most one of those two transactions will be accepted into a validated ledger. When the first transaction is validated itsTicket
is consumed. When the server sees the second transaction, it will note that the transaction is using aTicket
that is not in the ledger (since the first transaction consumed it) and could never be created. So the second transaction will fail with atef
code and never be forwarded to the network.Q: How do I know which
Ticket
s I can use?A: The
account_objects
RPC command answers only part of this question. It tells you whichTicket
s are available, in ledger, for use.However if you have several transactions simultaneously getting signed outside of the ledger, then you must personally take steps to not use the same
Ticket
in two or more of those transactions. If you have responsibility for only one account then tracking is easy; just note the integer value of eachTicketSequence
you use. If you are responsible for several accounts, then you must keep separate lists for each of the accounts.If several transaction creators are sharing one account, then they need to create a policy for which user can use which
Tickets
. Fixed schemes (e.g., Alice gets evenTicketSequence
s and Bob gets the odd ones) can get out of balance over time. Consider what happens with this policy if Alice writes a lot more transactions than Bob for a prolonged period.A more sustainable approach for multiple transaction creators is that each party gets an initial set of assigned
Ticket
s. From that point on each party createsTicket
s when they need them and uses onlyTicket
s they created. A bit of stewardship is still required in this model. For example, if Bob acquires 230Ticket
s, then Alice can create at most 20Ticket
s until Bob burns down his supply. People still need to be considerate of their cosigners.Appendix A: Specification
Transactions
TicketCreate Creates One Or More New Tickets
Parameters
Account
TicketCount
Sequence
TicketSequence
TicketSequence
number to consume for the transaction. The correspondingTicket
must already be in the ledger.The following example is a
TicketCreate
transaction that creates 250Ticket
s and includes 4 multisigners.Restrictions / Validation
Ticket
s in ledger as a consequence of thisTicketCreate
, then the transaction fails with atecDIR_FULL
.Ticket
s. OtherwisetecINSUFFICIENT_RESERVE
is returned.Ticket
s are created or none of them are.Who Can Issue
The account to which the
Ticket
(s) will be attached. Any signature that's valid for the account creating theTicket
(s) is acceptable.Notes
If successful, this transaction increments the issuer's owner count by the number of
Ticket
s created. It inserts tracking entries in the owner directory of theAccount
for each of the createdTicket
s.If the
TicketCreate
transaction uses aSequence
, then the first createdTicket
holds as itsTicketSequence
field a value one greater than theSequence
number of the transaction.If the
TicketCreate
transaction uses aTicketSequence
, then the first createdTicket
holds as itsTicketSequence
field the value of the account rootSequence
.Each subsequent
Ticket
created by the transaction holds as itsTicketSequence
field a value one greater than the previously createdTicket
.The final
Sequence
number on the account root after the transaction completes is one greater than theTicketSequence
value of the lastTicket
created.Consuming
Ticket
s In TransactionsAll transactions associated with an account (i.e., not pseudo-transactions) can use a
TicketSequence
in place of theSequence
. Doing this allows the transaction to be submitted to the network in any order and not be blocked by unvalidated transactions with earlier (or later)TicketSequence
orSequence
numbers.In order to submit a transaction that uses a
Ticket
instead of aSequence
you must make the following adjustments to the transaction.Sequence
Sequence
field must be present and must contain zero.TicketSequence
TicketSequence
field must be present and contain theTicketSequence
value of aTicket
that is currently owned by theAccount
.In order to allow
Ticket
s to be consumed by transactions, all transactions associated with an account allow an optionalTicketSequence
field.Ledger Objects
New Ledger Type
Ticket
with IdentifierltTICKET
A
Ticket
in the ledger has the following fields:sfAccount
Ticket
.sfTicketSequence
TicketSequence
, an unsigned 32-bit integer.sfOwnerNode
Ticket
in theTicket
owner's directory.sfPreviousTxnID
sfPreviousTxnLgrSeq
Ticket
Indexing FunctionChanges To
AccountRoot
To track the number of
Ticket
s an account owns, a new unsigned 32-bit optionalTicketCount
field is added to theAccountRoot
. If an account has one or moreTicket
s theTicketCount
is maintained to reflect the total owned. If the number of ownedTicket
s ever drops to zero, then the (optional) field is removed from theAccountRoot
.Changes To Amendments
The
Tickets
amendment has been in rippled for as long as there have been amendments. However this proposal introduces significant changes from the partialTickets
implementation that has been in place for the past years. In similar situations in the past we have chosen to use a new amendment, rather than use the old amendment name, to introduce the heavily modified feature.The motivation for using a new amendment name is as follows:
Whereas if we change the amendment name then an old version of the software would not be able to enable the amendment; even partial support is not present in the old version of the software. In this case the old version of the software would become amendment blocked, which is exactly what we want.
Therefore we have a new amendment,
TicketBatch
, to enable the new features.The preexisting
Tickets
amendment is removed. Even though theTickets
amendment has been available for a long time it has never been enabled. Since it was never enabled, removing the amendment will not leave any servers amendment blocked.RPC Commands
The following rippled RPC commands have been adjusted to accommodate
Tickets
:account_objects
The
account_objects
RPC command is modified to accept"type": "ticket"
in its arguments. This causes theaccount_objects
command to return all of theTicket
objects on the account. Tests for tickets inaccount_objects
are added.account_tx
The
account_tx
RPC command has been be tested withTicket
s. It looks like no modifications to that command are required in order to supportTicket
s.ledger_data
The
ledger_data
RPC command accepts an argument"type"
. (That"type"
argument is undocumented here, I don't know whether that's intentional.) The command is augmented to support"type": "ticket"
. This support occurred as a side effect of the fix foraccount_objects
. Tests for tickets inledger_data
are added.ledger_entry
The
ledger_entry
command is modified to return aTicket
object specified either as:Ticket
index as hexadecimal, or as"owner"
and a"ticket_sequence"
field.Tests for
Ticket
s inledger_entry
are added.Sorting, Collating, and Validating Transactions
The following elements are all internal to the rippled software, but are added or changed due to the addition of
Ticket
s.SeqProxy
Both the
CanonicalTXSet
andTxQ
need to solve the problem of putting transactions in order, and the rules are changing with the addition ofTicket
s. It was beneficial to add a new class that captures both theSequence
number and the optionalTicketSequence
value and provides an ordering.CanonicalTXSet
The
mSeq
member of theCanonicalTXSet::Key
type is replaced with aSeqProxy
. That new member is taken into account in theKey
comparison operators. That change has spread out to affect users of theCanonicalTXSet
.TxQ
The
TxQ
contains a fewstd::map
s that use the transaction'sSequence
number as the key. Those keys are replaced with aSeqProxy
object. That change has spread out and affected users of theTxQ
. Considerable effor was put into making theTxQ
andCanonicalTXSet
behave as similarly as possible.The
TxQ
has the concept of blocker transactions for an account. TheTxQ
considers a transaction to be a blocker if the transaction changes how later transactions on that account can be signed. So, for instance, aSetRegularKey
transaction is a blocker. The introduction ofTicket
s affects blocker handling by theTxQ
.Prior to the introduction of
Ticket
s theTxQ
allowed a blocker to follow other transactions in the account's queue. But once the blocker was in place no more transactions could be added for that account. This rule worked well because withoutTicket
s transactions for a single account were always applied in a well known order: eachSequence
had to immediately follow the precedingSequence
. WithTicket
s that expectation is no longer valid; transactions withTicket
s can go into the ledger in any order.So the rules for how the
TxQ
handles blockers has changed. A blocker can only be added to the queue for an account if:These rules have the effect that a blocker must always be alone in the account's queue. Then, once the blocker has flushed through to the ledger, that account's queue is again open for general use.
Transactor
The
Transactor::checkSeq()
method needed to take the presence ofTicket
s into account. That method has been adjusted and renamed tocheckSeqProxy()
. This method is called duringpreclaim
, so it accomplishes many of the requirements noted in Testing for Transaction Validity and Uniqueness.Impact in the SQLite Database
None identified, however tests were added to increase confidence.
The text was updated successfully, but these errors were encountered: