Open mDuo13 opened 6 years ago
I'll give this a close read, but I highly support revisiting better ways to do reliable transaction submission.
(Made slight edits to fix typos, add a link to the fields of the sign method, and clarify that the reliable submission store should not be on ephemeral storage.)
Yeah, ugh. And add FirstLedgerSeqence symmetric to LastLedgerSequence for a simple mental model, and don't cry about 4 bytes while using 160 bits for "USD" and endless 0s for inner nodes :)
I recommend a dumb peer (https://github.com/ripple/rippled/issues/2413) implemented on the JVM, that just tracks state from validators, and does not process transactions. With a plugin system for user commands (bots/whatever) and data processing :)
And useable as a library, and with a Pony with a ripple tattoo
Totally agree this should be in rippled, and yes, impossible to put RTS in client libs without handling persistence of txns before putting on the network for handling crash recovery.
Nice work pushing this Rome.
What is a txn id???
I'm not sure I understand everything you said, @sublimator. Are you suggesting that FirstLedgerSequence
should be added as a common transaction field? I think I could get behind that. It's not necessary for this proposal, but would be convenient for it.
Also, I'm adding a ledger_index
field which should appear in the result when the transaction is confirmed into a validated ledger.
noticed one typo in submission_status "unknown". Looks like the beginning didn't get deleted.
The transaction's Due to a gap in ledger history,
On thing I found counterintuitive is that submit_reliable_tx - is a superset of submit get_reliable_tx - is not a superset of tx
It looks like you need to first get_reliable_tx(), then tx(submitted_hashes[0]) Is that what you are thinking?
This looks awesome Rome. I highly support it! If building this into rippled itself turns out to be troublesome, then maybe build it as an optional executable. One that's packaged with rippled and designed to run on the same box.
On thing I found counterintuitive is that submit_reliable_tx - is a superset of submit get_reliable_tx - is not a superset of tx
It looks like you need to first get_reliable_tx(), then tx(submitted_hashes[0]) Is that what you are thinking?
Yes, that is what I was thinking. It makes some kind of sense to change get_reliable_tx
to be a superset of tx
so it returns the tx response somewhere rather than just pulling certain fields out of it, if the transaction is in a validated ledger. I guess you only ever have one reliably-submitted transaction actually end up in a ledger, so you wouldn't need to worry about including multiple "tx" responses. You would want to be extra clear and only include the tx response if the transaction is in a validated ledger.
I'll think on this and possibly update the spec to do that.
This may be bike-shedding at this point, but I think the status name failed
is going to cause confusion with people who don't read documentation. feeClaimed
seems more intuitive to me.
Rationale: Reliable/robust transaction submission is the biggest feature of Ripple-REST that isn't covered by RippleAPI. Despite the thorough documentation, this remains one of the hardest things to do properly when integrating a business with the XRP Ledger. Adding reliable transaction submission to a client library is infeasible since it involves running as a persistent service and persisting data (probably to a database). Those are both things that
rippled
does already. As an added bonus, putting this functionality inrippled
gives us access to tools to conveniently backfill ledger history in case of an outage. We can reduce barriers to entry in the XRP Ledger ecosystem by building reliable transaction submission intorippled
servers so businesses who runrippled
always and automatically have access to a quality implementation of reliable transaction submission.Prerequisites
To use this described implementation, users must have the following
rippled
serverwallet_propose
is admin-only. They don't pose risks to the server administrator, but users would be very unwise to use them on servers that they don't control.rippled
server must have non-ephemeral storage for persisting reliable transaction submissions. This storage can be separate from the ledger store and shard store (and may not need nearly as many IOPS).These requirements are very similar to the requirements for using Ripple-REST for reliable transaction submission, except that the user does not need to run a Node.js server with Ripple-REST.
Architecture
Add several new API methods for submitting transactions reliably and managing them. Because these API methods store secrets on behalf of the user, I suggest making them admin-only. Add a new data store, tentatively called the reliable submission store, for tracking reliably submitted transactions.
One or more backgrounds job should run as part of
rippled
to manage the state of any reliable transaction submissions whose current status is not final. These jobs' responsibilities include:LastLedgerSequence
values, and when submitting follow-up attempts to transactions that failed initially.)tec
codes are not retried, since the results of retrying such transactions are likely to be the same.)API
This proposal adds several new admin methods to the JSON-RPC and WebSocket APIs of
rippled
. The methods are (names are placeholders):submit_reliable_tx
: initiate reliable submission of a transactionget_reliable_tx
: check the status of a reliably-submitted txdelete_reliable_tx
: delete a reliable tx submission. (This does not directly affect the XRP Ledger data itself)TODO: Maybe also a
update_reliable_tx
method to update the settings of a reliably-submitted (but not final) tx?submit_reliable_tx
Submit a transaction for reliable submission.
The fields provided to this method are a superset of the fields supported by the
sign
method, except that theoffline
field of thesign
method is not allowed. The reliable transaction submission system persists all the values from this request, including the providedsecret
/seed
/seed_hex
/passphrase
, so that it can re-submit the transaction if appropriate. (Optionally, to reduce attack surface,rippled
can delete the saved secret value when the transaction's outcome is final.) Certain fields in thetx_json
may be omitted so thatrippled
can automatically fill them with appropriate values. Unlike with regular signing and submitting,rippled
can change the auto-filled values if it needs to re-sign and re-submit the transaction. Fields explicitly specified MUST NOT be changed even for resubmission. (Exception: adding thereliable_submission_id
to theMemos
array.) See "Modifying for Submission" below for details.In addition to the
sign
parameters, the user provides the following fields:reliable_submission_id
max_attempts
ledger_index_offset
LastLedgerSequence
relative to the current ledger. Defaults to 3.When it receives the request, the reliable submission system does the following things in order: (Note: To avoid failures, it's important that the following occur in the proper order.)
The data persisted to the reliable submission store includes:
submit_reliable_tx
request.max_attempts
,ledger_index_offset
,fee_mult_max
,fee_div_max
, andbuild_path
.submission_status
submitted
. See submission_status Valuessubmitted_hashes
max_attempts
. When initially persisted, this array is length 1, containing the hash of the first attempt (even before it has been submitted).min_ledger_index
ledger_index
at the time the reliable transaction submission was received. In other words, the earliest possible ledger version in which this transaction could appear.recent_last_ledger_sequence
LastLedgerSequence
of the most recent attempt at submitting the transaction.Note: The maximum number of ledger versions the system may have to search to confirm a transaction's final result may be greater than
ledger_index_offset
because the auto-filledLastLedgerSequence
is based on the current ledger while themin_ledger_index
is based on the latest validated ledger. (This is by design; if there's a delay in consensus, there may be several closed, unvalidated ledgers, and in that case, it is unlikely but possible for a newly-submitted transaction to be included in those ledger indexes. To maximize chances of the transaction succeeding, we set theLastLedgerSequence
based on the current ledger, but to avoid failing we search as far back as the earliest ledger index it could possibly appear in.)get_reliable_tx
Look up the status of a reliably-submitted transaction.
The request contains just one field:
reliable_submission_id
The response contains the entire saved reliable submission object, including:
reliable_submission_id
tx_json
secret
/seed
/passphrase
/seed_hex
sign
command. TODO: Optionally, this field can be deleted automatically if the transaction has a final result.key_type
secp256k1
ored25519
.build_path
Paths
field of the transaction. Note: Unlike thesign
command's current behavior, reliable transaction submission should use the value, not the presence, of this field.fee_mult_max
Fee
can be.fee_div_max
fee_mult_max
(1
if the request didn't specify it).submission_status
submitted
. See submission_status Valuessubmitted_hashes
max_attempts
.min_ledger_index
ledger_index
at the time the reliable transaction submission was received. In other words, the earliest possible ledger version in which this transaction could appear.recent_last_ledger_sequence
LastLedgerSequence
of the most recent attempt at submitting the transaction.max_attempts
ledger_index_offset
LastLedgerSequence
values)result
submission_status
issucceeded
,failed
, orrejected
)_ The transaction engine code of the final attempt at submitting the transaction. Ifsubmission_status
issucceeded
, this is alwaystesSUCCESS
. Ifsubmission_status
isfailed
, this is always atec
-class code. Ifsubmission_status
isrejected
, this is either atem
-class code,tefPAST_SEQ
, ortefMAX_LEDGER
.ledger_index
submission_status
issucceeded
orfailed
.)_ The validated ledger version in which this transaction appears.delete_reliable_tx
Remove a reliable submission object from the reliable submission store.
The request contains just one field:
reliable_submission_id
The response contains the last known state of the given reliable submission, in the same format as
get_reliable_tx
.This method deletes the reliable submission object, but it does not affect the processing of any attempts to process that transaction that have already been submitted. It does prevent future retries and aborts any backfilling that was necessary to determine the final status of this transaction.
Other Details
submission_status Values
The
submission_status
field of a Reliable Transaction Submission object has the following possible values:submitted
submitted_hashes
array is the identifying hash of the currently-pending version of the transaction. In extreme cases, a reliable submission may be persisted in this state even if the attempt at submitting the transaction failed due to an outage.queued
terQUEUED
, and the transaction has not yet been locally applied to any ledger version. If the transaction is included in an open or closed ledger (including as a result of switching to a different closed ledger received from peers in consensus), the status changes tosubmitted
orresubmitted
as appropriate. TODO: maybe remove this status in favor of just usingsubmitted
?resubmitted
submitted
status, element 0 of thesubmitted_hashes
array is the currently-pending transaction hash. Later elements of the array are the hashes of previous submission attempts. TODO: maybe remove this status in favor of just usingsubmitted
?succeeded
tesSUCCESS
result. In this case, element 0 of thesubmitted_hashes
array is the identifying hash of the transaction that succeeded.failed
tec
-class code). It will not be retried.rejected
tem
-class code), (2) the transaction had an explicitly-specifiedSequence
value but resulted intefPAST_SEQ
, (3) the transaction has been attemptedmax_attempts
times and the last attempt has aLastLedgerSequence
higher than the latest validated ledger version, or (4) the transaction instructions contain an explicitly specifiedLastLedgerSequence
parameter which is lower than the latest validated ledger version.unknown
rippled
tries to backfill ledger history to determine the final outcome. This may occur ifrippled
was stopped or lost power when a transaction's outcome was pending.Caution: There's a slight difference between case 3 of
rejected
and atefMAX_LEDGER
result. Any given attempt at submitting the transaction can fail withtefMAX_LEDGER
but the reliable transaction submission may not have failed finally if the transaction can be modified and submitted again. In other words, ifmax_attempts
is higher than the number of attempts made so far and the transaction instructions do not include a hard-codedLastLedgerSequence
parameter, atefMAX_LEDGER
result is not final. Another case in whichtefMAX_LEDGER
is not final is if it results from a closed-but-not-validated ledger version that's higher thanLastLedgerSequence
. If a different version of a closed ledger is validated by consensus, the transaction could still become included in a validated ledger.The following state diagram shows the possible transitions of
submission_status
values (for the proposal as written and for the variant whereresubmitted
andqueued
are removed):TODO: It's worth discussing how
unknown
status should interact with ledger history and online delete. The most user-friendly behavior, assuming these commands remain admin-only, is that the server should automatically backfill ledgers even in excess of the desired number of historical ledgers to save, and online delete should not delete ledgers in the range ofmin_ledger_index
torecent_last_ledger_sequence
(inclusive) while a reliable submission's status isunknown
. After the transaction's outcome is confirmed, it would be OK to delete those ledgers, but the ledger containing the transaction should not be deleted.Modifying for Submission
Before the transaction is signed and submitted,
rippled
automatically fills certain fields the same way it auto-fills them when doing online signing. In addition to that, reliable transaction submission automatically adds the following:reliable_submission_id
. If the transaction already has aMemos
field, this memo is appended to theMemos
array. See "Memo Format" for details.LastLedgerSequence
field with a value equal to the current ledger index plus theledger_index_offset
from the request.The reliable transaction submission system does not persist the automatically-provided fields in the reliable submission store. This is to distinguish between explicitly specified fields (which are not modified) and automatically-filled fields (which can be modified when resubmitting the transaction).
If a reliably-submitted transaction fails to be included in a ledger,
rippled
can attempt to resubmit the transaction. This may require modifying certain fields of the transaction. This should always be based on the persistedtx_json
instructions, not the signed instructions from any given attempt. In all cases except theMemo
field, the modified fields MUST NOT overwrite fields that were explicitly defined in thetx_json
of the original request.LastLedgerSequence
should be modified if a previous attempt failed to be include the transaction in a validated ledger before itsLastLedgerSequence
. The newLastLedgerSequence
value should be equal to the current ledger plus the persistedledger_index_offset
.LastLedgerSequence
will be equal to the oldLastLedgerSequence
plus theledger_index_offset
. However, this is not true in case of power outages or busy servers, where additional time may pass between when the initial submission fails and when it gets resubmitted.Sequence
field can be modified if the transaction results intefPAST_SEQ
. This is unlikely to occur unless a user is submitting transactions on multiple systems in parallel.Fee
field can be modified if the provisional result of the transaction was a failure due to an insufficient transaction cost. The newFee
value must not be higher than the amount defined by thefee_mult_max
andfee_div_max
values of the initial request. Result codes that should prompt an increased transaction cost includetelCAN_NOT_QUEUE
,telCAN_NOT_QUEUE_FEE
,telCAN_NOT_QUEUE_FULL
,telINSUF_FEE_P
,terINSUF_FEE_B
. TODO: Maybe this should just happen automatically based on the expected transaction cost at the time of resubmission and not based on the result of the previous submission, since transaction costs may increase coincidentally around the time of resubmission even if the previous attempt didn't fail for transaction cost reasons?Paths
of a Payment (ifbuild_path
was provided) can be updated at the time of resubmission.Memos
field should still be modified to include thereliable_submission_id
memo.)Note: The user may explicitly provide some auto-fillable fields, such as
Fee
,Sequence
, orLastLedgerSequence
. Those fields must never be modified, even though it may eliminate some opportunities to resubmit a transaction. For example, if the user provides aSequence
field and the sending account'sSequence
field in a validated ledger is or becomes higher than the reliably-submitted transaction'sSequence
field before even the first attempt is included in a validated ledger, the reliable submission object goes to therejected
status rather than being resubmitted with a higher sequence number.When resubmitting, the reliable submission system should do the following steps in order (similar to the initial attempt):
submitted_hashes
array.min_ledger_index
to the latest validated ledger index plus one.submission_status
to beresubmitted
.recent_last_ledger_sequence
with theLastLedgerSequence
of the updated transaction.Memo Format
When transactions are submitted using this mechanism,
rippled
adds a memo with thereliable_submission_id
so that you can recognize when a transaction is the result of a particular reliable-transaction-submission. The memo has the following format:MemoData
reliable_submission_id
UUID, in a binary big-endian encoding. (Not Microsoft's COM/OLE mixed-endian encoding.)MemoFormat
0x55554944
(the ASCII for "UUID"). Note: Surprisingly, there isn't a MIME type for UUIDs.