From 293b14868ab7d7eb9769d88ef65a63b91f011e6c Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 9 Dec 2024 13:27:33 -0600 Subject: [PATCH 1/2] Added new fatal error cases to txm error parser --- pkg/solana/txm/txm.go | 44 +++++++++++++--- pkg/solana/txm/txm_unit_test.go | 90 ++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/pkg/solana/txm/txm.go b/pkg/solana/txm/txm.go index 342f54dce..10cc1acd2 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -432,7 +432,7 @@ func (txm *Txm) confirm() { if res[i].Err != nil { // Process error to determine the corresponding state and type. // Skip marking as errored if error considered to not be a failure. - if txState, errType := txm.processError(s[i], res[i].Err, false); errType != NoFailure { + if txState, errType := txm.ProcessError(s[i], res[i].Err, false); errType != NoFailure { id, err := txm.txs.OnError(s[i], txm.cfg.TxRetentionTimeout(), txState, errType) if err != nil { txm.lggr.Infow(fmt.Sprintf("failed to mark transaction as %s", txState.String()), "id", id, "signature", s[i], "error", err) @@ -545,7 +545,7 @@ func (txm *Txm) simulate() { } // Process error to determine the corresponding state and type. // Certain errors can be considered not to be failures during simulation to allow the process to continue - if txState, errType := txm.processError(msg.signatures[0], res.Err, true); errType != NoFailure { + if txState, errType := txm.ProcessError(msg.signatures[0], res.Err, true); errType != NoFailure { id, err := txm.txs.OnError(msg.signatures[0], txm.cfg.TxRetentionTimeout(), txState, errType) if err != nil { txm.lggr.Errorw(fmt.Sprintf("failed to mark transaction as %s", txState.String()), "id", id, "err", err) @@ -702,7 +702,7 @@ func (txm *Txm) EstimateComputeUnitLimit(ctx context.Context, tx *solanaGo.Trans } // Process error to determine the corresponding state and type. // Certain errors can be considered not to be failures during simulation to allow the process to continue - if txState, errType := txm.processError(sig, res.Err, true); errType != NoFailure { + if txState, errType := txm.ProcessError(sig, res.Err, true); errType != NoFailure { err := txm.txs.OnPrebroadcastError(id, txm.cfg.TxRetentionTimeout(), txState, errType) if err != nil { return 0, fmt.Errorf("failed to process error %v for tx ID %s: %w", res.Err, id, err) @@ -746,7 +746,7 @@ func (txm *Txm) simulateTx(ctx context.Context, tx *solanaGo.Transaction) (res * } // processError parses and handles relevant errors found in simulation results -func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState TxState, errType TxErrType) { +func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState TxState, errType TxErrType) { if resErr != nil { // handle various errors // https://github.com/solana-labs/solana/blob/master/sdk/src/transaction/error.rs @@ -773,10 +773,6 @@ func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulat return txState, NoFailure } return Errored, errType - // transaction will encounter execution error/revert - case strings.Contains(errStr, "InstructionError"): - txm.lggr.Debugw("InstructionError", logValues...) - return Errored, errType // transaction is already processed in the chain case strings.Contains(errStr, "AlreadyProcessed"): txm.lggr.Debugw("AlreadyProcessed", logValues...) @@ -786,6 +782,38 @@ func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulat return txState, NoFailure } return Errored, errType + // transaction will encounter execution error/revert + case strings.Contains(errStr, "InstructionError"): + txm.lggr.Debugw("InstructionError", logValues...) + return FatallyErrored, errType + // transaction contains an invalid account reference + case strings.Contains(errStr, "InvalidAccountIndex"): + txm.lggr.Debugw("InvalidAccountIndex", logValues...) + return FatallyErrored, errType + // transaction loads a writable account that cannot be written + case strings.Contains(errStr, "InvalidWritableAccount"): + txm.lggr.Debugw("InvalidWritableAccount", logValues...) + return FatallyErrored, errType + // address lookup table not found + case strings.Contains(errStr, "AddressLookupTableNotFound"): + txm.lggr.Debugw("AddressLookupTableNotFound", logValues...) + return FatallyErrored, errType + // attempted to lookup addresses from an invalid account + case strings.Contains(errStr, "InvalidAddressLookupTableData"): + txm.lggr.Debugw("InvalidAddressLookupTableData", logValues...) + return FatallyErrored, errType + // address table lookup uses an invalid index + case strings.Contains(errStr, "InvalidAddressLookupTableIndex"): + txm.lggr.Debugw("InvalidAddressLookupTableIndex", logValues...) + return FatallyErrored, errType + // attempt to debit an account but found no record of a prior credit. + case strings.Contains(errStr, "AccountNotFound"): + txm.lggr.Debugw("AccountNotFound", logValues...) + return FatallyErrored, errType + // attempt to load a program that does not exist + case strings.Contains(errStr, "ProgramAccountNotFound"): + txm.lggr.Debugw("ProgramAccountNotFound", logValues...) + return FatallyErrored, errType // unrecognized errors (indicates more concerning failures) default: // if simulating, return TxFailSimOther if error unknown diff --git a/pkg/solana/txm/txm_unit_test.go b/pkg/solana/txm/txm_unit_test.go index 87803581f..89a77a1a7 100644 --- a/pkg/solana/txm/txm_unit_test.go +++ b/pkg/solana/txm/txm_unit_test.go @@ -2,6 +2,7 @@ package txm_test import ( "errors" + "fmt" "math/big" "testing" @@ -46,7 +47,6 @@ func TestTxm_EstimateComputeUnitLimit(t *testing.T) { lggr := logger.Test(t) cfg := config.NewDefault() client := clientmocks.NewReaderWriter(t) - require.NoError(t, err) loader := utils.NewLazyLoad(func() (solanaClient.ReaderWriter, error) { return client, nil }) txm := solanatxm.NewTxm("localnet", loader, nil, cfg, mkey, lggr) @@ -152,6 +152,94 @@ func TestTxm_EstimateComputeUnitLimit(t *testing.T) { }) } +func TestTxm_ProcessError(t *testing.T) { + t.Parallel() + + // setup mock keystore + mkey := keyMocks.NewSimpleKeystore(t) + // set up txm + lggr := logger.Test(t) + cfg := config.NewDefault() + client := clientmocks.NewReaderWriter(t) + loader := utils.NewLazyLoad(func() (solanaClient.ReaderWriter, error) { return client, nil }) + txm := solanatxm.NewTxm("localnet", loader, nil, cfg, mkey, lggr) + + t.Run("process BlockhashNotFound error", func(t *testing.T) { + t.Parallel() + err := map[string][]interface{}{ + "BlockhashNotFound": { + 0, map[string]int{"Custom": 6003}, + }, + } + // returns no failure if BlockhashNotFound encountered during simulation + txState, errType := txm.ProcessError(solana.Signature{}, err, true) + require.Equal(t, solanatxm.NoFailure, errType) + require.Equal(t, solanatxm.NotFound, txState) // default enum value + + // returns error if BlockhashNotFound encountered during normal processing + txState, errType = txm.ProcessError(solana.Signature{}, err, false) + require.Equal(t, solanatxm.TxFailRevert, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value + }) + t.Run("process AlreadyProcessed error", func(t *testing.T) { + t.Parallel() + err := map[string][]interface{}{ + "AlreadyProcessed": { + 0, map[string]int{"Custom": 6003}, + }, + } + // returns no failure if AlreadyProcessed encountered during simulation + txState, errType := txm.ProcessError(solana.Signature{}, err, true) + require.Equal(t, solanatxm.NoFailure, errType) + require.Equal(t, solanatxm.NotFound, txState) // default enum value + + // returns error if AlreadyProcessed encountered during normal processing + txState, errType = txm.ProcessError(solana.Signature{}, err, false) + require.Equal(t, solanatxm.TxFailRevert, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value + }) + t.Run("process fatal error cases", func(t *testing.T) { + t.Parallel() + fatalErrorCases := []string{"InstructionError", "InvalidAccountIndex", "InvalidWritableAccount", "AddressLookupTableNotFound", "InvalidAddressLookupTableData", "InvalidAddressLookupTableIndex", "AccountNotFound", "ProgramAccountNotFound"} + for _, errCase := range fatalErrorCases { + t.Run(fmt.Sprintf("process %s error", errCase), func(t *testing.T) { + t.Parallel() + err := map[string][]interface{}{ + errCase: { + 0, map[string]int{"Custom": 6003}, + }, + } + // returns fatal error if InstructionError encountered during simulation + txState, errType := txm.ProcessError(solana.Signature{}, err, true) + require.Equal(t, solanatxm.TxFailSimRevert, errType) + require.Equal(t, solanatxm.FatallyErrored, txState) // default enum value + + // returns fatal error if InstructionError encountered during normal processing + txState, errType = txm.ProcessError(solana.Signature{}, err, false) + require.Equal(t, solanatxm.TxFailRevert, errType) + require.Equal(t, solanatxm.FatallyErrored, txState) // default enum value + }) + } + }) + t.Run("process unknown error", func(t *testing.T) { + t.Parallel() + err := map[string][]interface{}{ + "MadeUpError": { + 0, map[string]int{"Custom": 6003}, + }, + } + // returns fatal error if InstructionError encountered during simulation + txState, errType := txm.ProcessError(solana.Signature{}, err, true) + require.Equal(t, solanatxm.TxFailSimOther, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value + + // returns fatal error if InstructionError encountered during normal processing + txState, errType = txm.ProcessError(solana.Signature{}, err, false) + require.Equal(t, solanatxm.TxFailRevert, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value + }) +} + func createTx(t *testing.T, client solanaClient.ReaderWriter, signer solana.PublicKey, sender solana.PublicKey, receiver solana.PublicKey, amt uint64) *solana.Transaction { // create transfer tx hash, err := client.LatestBlockhash(tests.Context(t)) From c210b71647c2dbd2993485fda9351bff588ae1c9 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 9 Dec 2024 13:49:02 -0600 Subject: [PATCH 2/2] Fixed tests and linting --- pkg/solana/txm/txm_internal_test.go | 4 ++-- pkg/solana/txm/txm_unit_test.go | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/solana/txm/txm_internal_test.go b/pkg/solana/txm/txm_internal_test.go index 418bdbec1..0054e0a2b 100644 --- a/pkg/solana/txm/txm_internal_test.go +++ b/pkg/solana/txm/txm_internal_test.go @@ -935,7 +935,7 @@ func TestTxm_disabled_confirm_timeout_with_retention(t *testing.T) { // check transaction status which should still be stored status, err := txm.GetTransactionStatus(ctx, testTxID) require.NoError(t, err) - require.Equal(t, types.Failed, status) + require.Equal(t, types.Fatal, status) // Sleep until retention period has passed for transaction and for another reap cycle to run time.Sleep(15 * time.Second) @@ -1089,7 +1089,7 @@ func TestTxm_compute_unit_limit_estimation(t *testing.T) { // tx should be stored in-memory and moved to errored state status, err := txm.GetTransactionStatus(ctx, txID) require.NoError(t, err) - require.Equal(t, commontypes.Failed, status) + require.Equal(t, commontypes.Fatal, status) }) } diff --git a/pkg/solana/txm/txm_unit_test.go b/pkg/solana/txm/txm_unit_test.go index 89a77a1a7..7dfec6c57 100644 --- a/pkg/solana/txm/txm_unit_test.go +++ b/pkg/solana/txm/txm_unit_test.go @@ -223,20 +223,20 @@ func TestTxm_ProcessError(t *testing.T) { }) t.Run("process unknown error", func(t *testing.T) { t.Parallel() - err := map[string][]interface{}{ - "MadeUpError": { - 0, map[string]int{"Custom": 6003}, - }, - } - // returns fatal error if InstructionError encountered during simulation - txState, errType := txm.ProcessError(solana.Signature{}, err, true) - require.Equal(t, solanatxm.TxFailSimOther, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + err := map[string][]interface{}{ + "MadeUpError": { + 0, map[string]int{"Custom": 6003}, + }, + } + // returns fatal error if InstructionError encountered during simulation + txState, errType := txm.ProcessError(solana.Signature{}, err, true) + require.Equal(t, solanatxm.TxFailSimOther, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value - // returns fatal error if InstructionError encountered during normal processing - txState, errType = txm.ProcessError(solana.Signature{}, err, false) - require.Equal(t, solanatxm.TxFailRevert, errType) - require.Equal(t, solanatxm.Errored, txState) // default enum value + // returns fatal error if InstructionError encountered during normal processing + txState, errType = txm.ProcessError(solana.Signature{}, err, false) + require.Equal(t, solanatxm.TxFailRevert, errType) + require.Equal(t, solanatxm.Errored, txState) // default enum value }) }