Skip to content
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

Add new fatal error cases to txm error parser #966

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions pkg/solana/txm/txm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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...)
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/solana/txm/txm_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})
}

Expand Down
90 changes: 89 additions & 1 deletion pkg/solana/txm/txm_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package txm_test

import (
"errors"
"fmt"
"math/big"
"testing"

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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))
Expand Down
Loading