From 88b6e5d8e083df5b24b074a10711ad20be469889 Mon Sep 17 00:00:00 2001 From: Dzmitry Hil Date: Thu, 16 Nov 2023 16:02:27 +0300 Subject: [PATCH 1/3] Add XRPL tx finality check. --- relayer/processes/xrpl_tx_observer.go | 25 +++++++++++++++++++++ relayer/processes/xrpl_tx_observer_test.go | 26 +++++++++++++++++++++- relayer/processes/xrpl_tx_submitter.go | 2 +- relayer/xrpl/constatns.go | 14 +++++++----- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/relayer/processes/xrpl_tx_observer.go b/relayer/processes/xrpl_tx_observer.go index a00da145..1618ef02 100644 --- a/relayer/processes/xrpl_tx_observer.go +++ b/relayer/processes/xrpl_tx_observer.go @@ -2,6 +2,7 @@ package processes import ( "context" + "strings" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" @@ -78,6 +79,10 @@ func (o *XRPLTxObserver) Start(ctx context.Context) error { func (o *XRPLTxObserver) processTx(ctx context.Context, tx rippledata.TransactionWithMetaData) error { ctx = tracing.WithTracingXRPLTxHash(tracing.WithTracingID(ctx), tx.GetHash().String()) + if !txIsFinal(tx) { + o.log.Debug(ctx, "Transaction is not final", logger.StringField("txStatus", tx.MetaData.TransactionResult.String())) + return nil + } if o.cfg.BridgeAccount == tx.GetBase().Account { return o.processOutgoingTx(ctx, tx) } @@ -255,6 +260,26 @@ func (o *XRPLTxObserver) sendXRPLTrustSetTransactionResultEvidence(ctx context.C return err } +// txIsFinal returns value which indicates whether the transaction if final and can be used. +// Result Code Finality. +// tesSUCCESS Final when included in a validated ledger. +// Any tec code Final when included in a validated ledger. +// Any tem code Final unless the protocol changes to make the transaction valid. +// tefPAST_SEQ Final when another transaction with the same sequence number is included in a validated ledger. +// tefMAX_LEDGER Final when a validated ledger has a ledger index higher than the transaction's LastLedgerSequence field, and no validated ledger includes the transaction. +func txIsFinal(tx rippledata.TransactionWithMetaData) bool { + txResult := tx.MetaData.TransactionResult + if tx.MetaData.TransactionResult.Success() || + strings.HasPrefix(txResult.String(), xrpl.TecTxResultPrefix) || + strings.HasPrefix(txResult.String(), xrpl.TemTxResultPrefix) || + txResult.String() == xrpl.TefPastSeqTxResult || + txResult.String() == xrpl.TefMaxLedgerTxResult { + return true + } + + return false +} + func extractTicketSequencesFromMetaData(metaData rippledata.MetaData) []uint32 { ticketSequences := make([]uint32, 0) for _, node := range metaData.AffectedNodes { diff --git a/relayer/processes/xrpl_tx_observer_test.go b/relayer/processes/xrpl_tx_observer_test.go index 9f287515..c1533017 100644 --- a/relayer/processes/xrpl_tx_observer_test.go +++ b/relayer/processes/xrpl_tx_observer_test.go @@ -22,7 +22,10 @@ func TestXRPLTxObserver_Start(t *testing.T) { bridgeXRPLAddress := xrpl.GenPrivKeyTxSigner().Account() issuerAccount := xrpl.GenPrivKeyTxSigner().Account() - failTxResult := rippledata.TransactionResult(111) + // tecPATH_PARTIAL + failTxResult := rippledata.TransactionResult(101) + // tefBAD_AUTH + notTxResult := rippledata.TransactionResult(-199) relayerAddress := coreum.GenAccount() coreumRecipientAddress := coreum.GenAccount() @@ -124,6 +127,27 @@ func TestXRPLTxObserver_Start(t *testing.T) { return xrplAccountTxScannerMock }, }, + { + name: "incoming_not_confirmed_tx", + txScannerBuilder: func(ctrl *gomock.Controller, cancel func()) processes.XRPLAccountTxScanner { + xrplAccountTxScannerMock := NewMockXRPLAccountTxScanner(ctrl) + xrplAccountTxScannerMock.EXPECT().ScanTxs(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, ch chan<- rippledata.TransactionWithMetaData) error { + go func() { + ch <- rippledata.TransactionWithMetaData{ + Transaction: &rippledata.Payment{}, + MetaData: rippledata.MetaData{ + TransactionResult: notTxResult, + }, + } + cancel() + }() + return nil + }) + + return xrplAccountTxScannerMock + }, + }, { name: "incoming_not_payment_tx", txScannerBuilder: func(ctrl *gomock.Controller, cancel func()) processes.XRPLAccountTxScanner { diff --git a/relayer/processes/xrpl_tx_submitter.go b/relayer/processes/xrpl_tx_submitter.go index 46068247..0caadd92 100644 --- a/relayer/processes/xrpl_tx_submitter.go +++ b/relayer/processes/xrpl_tx_submitter.go @@ -218,7 +218,7 @@ func (s *XRPLTxSubmitter) signOrSubmitOperation(ctx context.Context, operation c } switch txRes.EngineResult.String() { - case xrpl.TefNOTicketTxResult, xrpl.TefPastSeqTxResult, xrpl.TerPreSeqTxResult: + case xrpl.TefNOTicketTxResult, xrpl.TefPastSeqTxResult: s.log.Debug(ctx, "Transaction has been already submitted", logger.StringField("txHash", tx.GetHash().String())) return nil case xrpl.TecInsufficientReserveTxResult: diff --git a/relayer/xrpl/constatns.go b/relayer/xrpl/constatns.go index d23bd63a..f7d1bb2b 100644 --- a/relayer/xrpl/constatns.go +++ b/relayer/xrpl/constatns.go @@ -1,12 +1,16 @@ package xrpl const ( - // TefNOTicketTxResult defines the result which indicates the usage of the passed ticket or not created ticket. + // TefNOTicketTxResult defines that the usage of the passed ticket or not created ticket. TefNOTicketTxResult = "tefNO_TICKET" - // TefPastSeqTxResult defines the result which indicates the usage of the sequence in the past. + // TefPastSeqTxResult defines that the usage of the sequence in the past. TefPastSeqTxResult = "tefPAST_SEQ" - // TerPreSeqTxResult defines the result which indicates the usage of the sequence in the future. - TerPreSeqTxResult = "terPRE_SEQ" - // TecInsufficientReserveTxResult defines the result which indicates the insufficient reserve to complete requested operation. + // TefMaxLedgerTxResult defines that ledger sequence too high. + TefMaxLedgerTxResult = "tefMAX_LEDGER" + // TecInsufficientReserveTxResult defines that reserve is insufficient to complete requested operation. TecInsufficientReserveTxResult = "tecINSUFFICIENT_RESERVE" + // TecTxResultPrefix is `tec` prefix for tx result. + TecTxResultPrefix = "tec" + // TemTxResultPrefix is `tem` prefix for tx result. + TemTxResultPrefix = "tem" ) From ec6b13639c3d956d80508cf61bc493f8b1ef31e2 Mon Sep 17 00:00:00 2001 From: Dzmitry Hil Date: Mon, 20 Nov 2023 15:55:24 +0300 Subject: [PATCH 2/3] Remove useless return --- relayer/processes/xrpl_tx_observer.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/relayer/processes/xrpl_tx_observer.go b/relayer/processes/xrpl_tx_observer.go index 1618ef02..27d50a92 100644 --- a/relayer/processes/xrpl_tx_observer.go +++ b/relayer/processes/xrpl_tx_observer.go @@ -269,15 +269,11 @@ func (o *XRPLTxObserver) sendXRPLTrustSetTransactionResultEvidence(ctx context.C // tefMAX_LEDGER Final when a validated ledger has a ledger index higher than the transaction's LastLedgerSequence field, and no validated ledger includes the transaction. func txIsFinal(tx rippledata.TransactionWithMetaData) bool { txResult := tx.MetaData.TransactionResult - if tx.MetaData.TransactionResult.Success() || + return tx.MetaData.TransactionResult.Success() || strings.HasPrefix(txResult.String(), xrpl.TecTxResultPrefix) || strings.HasPrefix(txResult.String(), xrpl.TemTxResultPrefix) || txResult.String() == xrpl.TefPastSeqTxResult || - txResult.String() == xrpl.TefMaxLedgerTxResult { - return true - } - - return false + txResult.String() == xrpl.TefMaxLedgerTxResult } func extractTicketSequencesFromMetaData(metaData rippledata.MetaData) []uint32 { From 227831c4670b196ae3fca59510fd4d68d147aba5 Mon Sep 17 00:00:00 2001 From: Dzmitry Hil Date: Fri, 24 Nov 2023 15:47:08 +0300 Subject: [PATCH 3/3] Remove duplicated comment --- relayer/xrpl/constatns.go | 1 - 1 file changed, 1 deletion(-) diff --git a/relayer/xrpl/constatns.go b/relayer/xrpl/constatns.go index 8e965e10..3c15c5f7 100644 --- a/relayer/xrpl/constatns.go +++ b/relayer/xrpl/constatns.go @@ -5,7 +5,6 @@ const ( // This could mean that the source and destination accounts are not linked by trust lines. TecPathDryTxResult = "tecPATH_DRY" // TefNOTicketTxResult defines the result which indicates the usage of the passed ticket or not created ticket. - // TefNOTicketTxResult defines that the usage of the passed ticket or not created ticket. TefNOTicketTxResult = "tefNO_TICKET" // TefPastSeqTxResult defines that the usage of the sequence in the past. TefPastSeqTxResult = "tefPAST_SEQ"