From 75efc7df1badb6fe9b6d9fbcb56a5a4001db12e4 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Tue, 4 Nov 2025 15:57:12 -0500 Subject: [PATCH 01/18] use sdk legacy --- mempool/txpool/legacypool/legacypool.go | 33 +++++--------------- mempool/txpool/legacypool/legacypool_test.go | 13 ++++---- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/mempool/txpool/legacypool/legacypool.go b/mempool/txpool/legacypool/legacypool.go index 7139a3b9d..4faccebdf 100644 --- a/mempool/txpool/legacypool/legacypool.go +++ b/mempool/txpool/legacypool/legacypool.go @@ -18,7 +18,6 @@ package legacypool import ( - "errors" "maps" "math/big" "slices" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/kzg4844" @@ -58,25 +58,6 @@ const ( txMaxSize = 4 * txSlotSize // 128KB ) -var ( - // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept - // another remote transaction. - ErrTxPoolOverflow = errors.New("txpool is full") - - // ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped - // nonce received from the accounts with delegation or pending delegation. - ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts") - - // ErrAuthorityReserved is returned if a transaction has an authorization - // signed by an address which already has in-flight transactions known to the - // pool. - ErrAuthorityReserved = errors.New("authority already reserved") - - // ErrFutureReplacePending is returned if a future transaction replaces a pending - // one. Future transactions should only be able to replace other future transactions. - ErrFutureReplacePending = errors.New("future transaction tries to replace pending") -) - var ( evictionInterval = time.Minute // Time interval to check for evictable transactions statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats @@ -623,7 +604,7 @@ func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error { if pending == nil { // Transaction with gapped nonce is not supported for delegated accounts if pool.pendingNonces.get(from) != tx.Nonce() { - return ErrOutOfOrderTxFromDelegated + return legacypool.ErrOutOfOrderTxFromDelegated } return nil } @@ -654,7 +635,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { count += queue.Len() } if count > 1 { - return ErrAuthorityReserved + return legacypool.ErrAuthorityReserved } // Because there is no exclusive lock held between different subpools // when processing transactions, the SetCode transaction may be accepted @@ -665,7 +646,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { // that attackers cannot easily stack a SetCode transaction when the sender // is reserved by other pools. if pool.reserver.Has(auth) { - return ErrAuthorityReserved + return legacypool.ErrAuthorityReserved } } } @@ -730,7 +711,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { // replacements to 25% of the slots if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) { throttleTxMeter.Mark(1) - return false, ErrTxPoolOverflow + return false, legacypool.ErrTxPoolOverflow } // New transaction is better than our worse ones, make room for it. @@ -741,7 +722,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { if !success { log.Trace("Discarding overflown transaction", "hash", hash) overflowedTxMeter.Mark(1) - return false, ErrTxPoolOverflow + return false, legacypool.ErrTxPoolOverflow } // If the new transaction is a future transaction it should never churn pending transactions @@ -760,7 +741,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { pool.priced.Put(dropTx) } log.Trace("Discarding future transaction replacing pending tx", "hash", hash) - return false, ErrFutureReplacePending + return false, legacypool.ErrFutureReplacePending } } diff --git a/mempool/txpool/legacypool/legacypool_test.go b/mempool/txpool/legacypool/legacypool_test.go index f0ed2dfb7..89ac36263 100644 --- a/mempool/txpool/legacypool/legacypool_test.go +++ b/mempool/txpool/legacypool/legacypool_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -1696,8 +1697,8 @@ func TestUnderpricing(t *testing.T) { t.Fatalf("failed to add well priced transaction: %v", err) } // Ensure that replacing a pending transaction with a future transaction fails - if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); !errors.Is(err, ErrFutureReplacePending) { - t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending) + if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); !errors.Is(err, legacypool.ErrFutureReplacePending) { + t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, legacypool.ErrFutureReplacePending) } pending, queued = pool.Stats() if pending != 4 { @@ -2297,8 +2298,8 @@ func TestSetCodeTransactions(t *testing.T) { statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) // Send gapped transaction, it should be rejected. - if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err) + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, legacypool.ErrOutOfOrderTxFromDelegated) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, legacypool.ErrOutOfOrderTxFromDelegated, err) } // Send transactions. First is accepted, second is rejected. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { @@ -2377,8 +2378,8 @@ func TestSetCodeTransactions(t *testing.T) { t.Fatalf("%s: failed to add with pending delegation: %v", name, err) } // Delegation rejected since two txs are already in-flight. - if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); !errors.Is(err, ErrAuthorityReserved) { - t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAuthorityReserved, err) + if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); !errors.Is(err, legacypool.ErrAuthorityReserved) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, legacypool.ErrAuthorityReserved, err) } }, }, From 05cff08cb1743396d227ecb1d470eedf86a9085e Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 10:11:51 -0500 Subject: [PATCH 02/18] consilidate error checking func --- mempool/txpool/locals/errors.go | 37 ++--- mempool/txpool/locals/errors_test.go | 3 +- rpc/backend/call_tx.go | 212 +++++++++++++++++++++------ rpc/backend/sign_tx.go | 123 ---------------- 4 files changed, 187 insertions(+), 188 deletions(-) diff --git a/mempool/txpool/locals/errors.go b/mempool/txpool/locals/errors.go index 2eca36b8e..641cbcfa4 100644 --- a/mempool/txpool/locals/errors.go +++ b/mempool/txpool/locals/errors.go @@ -2,29 +2,32 @@ package locals import ( "errors" + "strings" "github.com/cosmos/evm/mempool/txpool" - "github.com/cosmos/evm/mempool/txpool/legacypool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" ) -// IsTemporaryReject determines whether the given error indicates a temporary -// reason to reject a transaction from being included in the txpool. The result -// may change if the txpool's state changes later. +var ( + // ErrNonceGap is returned if the tx nonce is higher than the account nonce. + // This is a duplicate of mempool.ErrNonceGap to avoid import cycle. + ErrNonceGap = errors.New("tx nonce is higher than account nonce") +) + +// IsTemporaryReject determines whether the given error indicates a temporary reason to reject a +// transaction from being included in the txpool. The result may change if the txpool's state changes later. +// We use strings.Contains instead of errors.Is because we are passing in rawLog errors. func IsTemporaryReject(err error) bool { switch { - case errors.Is(err, legacypool.ErrOutOfOrderTxFromDelegated): - return true - case errors.Is(err, txpool.ErrInflightTxLimitReached): - return true - case errors.Is(err, legacypool.ErrAuthorityReserved): + case strings.Contains(err.Error(), legacypool.ErrOutOfOrderTxFromDelegated.Error()): + case strings.Contains(err.Error(), txpool.ErrInflightTxLimitReached.Error()): + case strings.Contains(err.Error(), legacypool.ErrAuthorityReserved.Error()): + case strings.Contains(err.Error(), txpool.ErrUnderpriced.Error()): + case strings.Contains(err.Error(), legacypool.ErrTxPoolOverflow.Error()): + case strings.Contains(err.Error(), legacypool.ErrFutureReplacePending.Error()): + case strings.Contains(err.Error(), ErrNonceGap.Error()): return true - case errors.Is(err, txpool.ErrUnderpriced): - return true - case errors.Is(err, legacypool.ErrTxPoolOverflow): - return true - case errors.Is(err, legacypool.ErrFutureReplacePending): - return true - default: - return false } + + return false } diff --git a/mempool/txpool/locals/errors_test.go b/mempool/txpool/locals/errors_test.go index a163131b7..902f703a1 100644 --- a/mempool/txpool/locals/errors_test.go +++ b/mempool/txpool/locals/errors_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/cosmos/evm/mempool/txpool" - "github.com/cosmos/evm/mempool/txpool/legacypool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" ) func TestIsTemporaryReject_PositiveCases(t *testing.T) { @@ -19,6 +19,7 @@ func TestIsTemporaryReject_PositiveCases(t *testing.T) { {name: "underpriced", err: txpool.ErrUnderpriced}, {name: "txpool overflow", err: legacypool.ErrTxPoolOverflow}, {name: "future replace pending", err: legacypool.ErrFutureReplacePending}, + {name: "tx nonce is higher than account nonce", err: ErrNonceGap}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index 5c65bce3b..14f41227b 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -8,6 +8,7 @@ import ( "math/big" "strings" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -17,6 +18,7 @@ import ( "google.golang.org/grpc/status" "github.com/cosmos/evm/mempool" + "github.com/cosmos/evm/mempool/txpool/locals" txlocals "github.com/cosmos/evm/mempool/txpool/locals" rpctypes "github.com/cosmos/evm/rpc/types" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -27,77 +29,116 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Resend accepts an existing transaction and a new gas price and limit. It will remove -// the given transaction from the pool and reinsert it with the new gas price and limit. -func (b *Backend) Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { - if args.Nonce == nil { - return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") +// SendTransaction sends transaction based on received args using Node's key to sign it +func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) { + // Look up the wallet containing the requested signer + if !b.Cfg.JSONRPC.AllowInsecureUnlock { + b.Logger.Debug("account unlock with HTTP access is forbidden") + return common.Hash{}, fmt.Errorf("account unlock with HTTP access is forbidden") } - args, err := b.SetTxDefaults(args) + _, err := b.ClientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.GetFrom().Bytes())) if err != nil { - return common.Hash{}, err + b.Logger.Error("failed to find key in keyring", "address", args.GetFrom(), "error", err.Error()) + return common.Hash{}, fmt.Errorf("failed to find key in the node's keyring; %s; %s", keystore.ErrNoMatch, err.Error()) } - // The signer used should always be the 'latest' known one because we expect - // signers to be backwards-compatible with old transactions. - cfg := b.ChainConfig() - if cfg == nil { - cfg = evmtypes.DefaultChainConfig(b.EvmChainID.Uint64()).EthereumConfig(nil) + if args.ChainID != nil && (b.EvmChainID).Cmp((*big.Int)(args.ChainID)) != 0 { + return common.Hash{}, fmt.Errorf("chainId does not match node's (have=%v, want=%v)", args.ChainID, (*hexutil.Big)(b.EvmChainID)) } - signer := ethtypes.LatestSigner(cfg) + args, err = b.SetTxDefaults(args) + if err != nil { + return common.Hash{}, err + } - matchTx := args.ToTransaction(ethtypes.LegacyTxType) + bn, err := b.BlockNumber() + if err != nil { + b.Logger.Debug("failed to fetch latest block number", "error", err.Error()) + return common.Hash{}, err + } - // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - price := matchTx.GasPrice() - if gasPrice != nil { - price = gasPrice.ToInt() + header, err := b.CurrentHeader() + if err != nil { + return common.Hash{}, err } - gas := matchTx.Gas() - if gasLimit != nil { - gas = uint64(*gasLimit) + + signer := ethtypes.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn)), header.Time) + + // LegacyTx derives EvmChainID from the signature. To make sure the msg.ValidateBasic makes + // the corresponding EvmChainID validation, we need to sign the transaction before calling it + + // Sign transaction + msg := evmtypes.NewTxFromArgs(&args) + if err := msg.Sign(signer, b.ClientCtx.Keyring); err != nil { + b.Logger.Debug("failed to sign tx", "error", err.Error()) + return common.Hash{}, err } - if err := rpctypes.CheckTxFee(price, gas, b.RPCTxFeeCap()); err != nil { + + if err := msg.ValidateBasic(); err != nil { + b.Logger.Debug("tx failed basic validation", "error", err.Error()) return common.Hash{}, err } - pending, err := b.PendingTransactions() + baseDenom := evmtypes.GetEVMCoinDenom() + + // Assemble transaction from fields + tx, err := msg.BuildTx(b.ClientCtx.TxConfig.NewTxBuilder(), baseDenom) if err != nil { + b.Logger.Error("build cosmos tx failed", "error", err.Error()) return common.Hash{}, err } - for _, tx := range pending { - // FIXME does Resend api possible at all? https://github.com/evmos/ethermint/issues/905 - p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{}) - if err != nil { - // not valid ethereum tx - continue - } + // Encode transaction by default Tx encoder + txEncoder := b.ClientCtx.TxConfig.TxEncoder() + txBytes, err := txEncoder(tx) + if err != nil { + b.Logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) + return common.Hash{}, err + } - pTx := p.AsTransaction() + ethTx := msg.AsTransaction() - wantSigHash := signer.Hash(matchTx) - pFrom, err := ethtypes.Sender(signer, pTx) - if err != nil { - continue - } + // check the local node config in case unprotected txs are disabled + if !b.UnprotectedAllowed() && !ethTx.Protected() { + // Ensure only eip155 signed transactions are submitted if EIP155Required is set. + return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") + } - if pFrom == *args.From && signer.Hash(pTx) == wantSigHash { - // Match. Re-sign and send the transaction. - if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { - args.GasPrice = gasPrice - } - if gasLimit != nil && *gasLimit != 0 { - args.Gas = gasLimit - } + txHash := ethTx.Hash() - return b.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice + // Broadcast transaction in sync mode (default) + // NOTE: If error is encountered on the node, the broadcast will not return an error + syncCtx := b.ClientCtx.WithBroadcastMode(flags.BroadcastSync) + rsp, err := syncCtx.BroadcastTx(txBytes) + if rsp != nil && rsp.Code != 0 { + err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) + } + if err != nil { + // Check if this is a nonce gap error that was successfully queued + if b.Mempool != nil && strings.Contains(err.Error(), mempool.ErrNonceGap.Error()) { + // Transaction was successfully queued due to nonce gap, return success to client + b.Logger.Debug("transaction queued due to nonce gap", "hash", txHash.Hex()) + // Track as local for priority and persistence + b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) + return txHash, nil } + // Temporary txpool rejections should be locally tracked for resubmission + if b.Mempool != nil && txlocals.IsTemporaryReject(err) { + b.Logger.Debug("temporary rejection, tracking locally", "hash", txHash.Hex(), "err", err.Error()) + b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) + return txHash, nil + } + b.Logger.Error("failed to broadcast tx", "error", err.Error()) + return txHash, err } - return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) + // Return transaction hash + // On success, track as local too to persist across restarts until mined + if b.Mempool != nil { + b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) + } + return txHash, nil } // SendRawTransaction send a raw Ethereum transaction. @@ -151,7 +192,11 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { syncCtx := b.ClientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) - if rsp != nil && rsp.Code != 0 { + if b.Mempool != nil && rsp != nil && rsp.Code != 0 { + if shouldSkip := locals.IsTemporaryReject(errors.New(rsp.RawLog)); shouldSkip { + b.Logger.Debug("temporary rejection, tracking locally", "tx_hash", txHash.Hex()) + return txHash, nil + } err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) } if err != nil { @@ -201,6 +246,79 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { return txHash, nil } +// Resend accepts an existing transaction and a new gas price and limit. It will remove +// the given transaction from the pool and reinsert it with the new gas price and limit. +func (b *Backend) Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { + if args.Nonce == nil { + return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") + } + + args, err := b.SetTxDefaults(args) + if err != nil { + return common.Hash{}, err + } + + // The signer used should always be the 'latest' known one because we expect + // signers to be backwards-compatible with old transactions. + cfg := b.ChainConfig() + if cfg == nil { + cfg = evmtypes.DefaultChainConfig(b.EvmChainID.Uint64()).EthereumConfig(nil) + } + + signer := ethtypes.LatestSigner(cfg) + + matchTx := args.ToTransaction(ethtypes.LegacyTxType) + + // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. + price := matchTx.GasPrice() + if gasPrice != nil { + price = gasPrice.ToInt() + } + gas := matchTx.Gas() + if gasLimit != nil { + gas = uint64(*gasLimit) + } + if err := rpctypes.CheckTxFee(price, gas, b.RPCTxFeeCap()); err != nil { + return common.Hash{}, err + } + + pending, err := b.PendingTransactions() + if err != nil { + return common.Hash{}, err + } + + for _, tx := range pending { + // FIXME does Resend api possible at all? https://github.com/evmos/ethermint/issues/905 + p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{}) + if err != nil { + // not valid ethereum tx + continue + } + + pTx := p.AsTransaction() + + wantSigHash := signer.Hash(matchTx) + pFrom, err := ethtypes.Sender(signer, pTx) + if err != nil { + continue + } + + if pFrom == *args.From && signer.Hash(pTx) == wantSigHash { + // Match. Re-sign and send the transaction. + if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { + args.GasPrice = gasPrice + } + if gasLimit != nil && *gasLimit != 0 { + args.Gas = gasLimit + } + + return b.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice + } + } + + return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) +} + // SetTxDefaults populates tx message with default values in case they are not // provided on the args func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) { diff --git a/rpc/backend/sign_tx.go b/rpc/backend/sign_tx.go index a8bd67437..19d444507 100644 --- a/rpc/backend/sign_tx.go +++ b/rpc/backend/sign_tx.go @@ -1,141 +1,18 @@ package backend import ( - "errors" "fmt" - "math/big" - "strings" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/signer/core/apitypes" - "github.com/cosmos/evm/mempool" - txlocals "github.com/cosmos/evm/mempool/txpool/locals" - evmtypes "github.com/cosmos/evm/x/vm/types" - - errorsmod "cosmossdk.io/errors" - - "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" ) -// SendTransaction sends transaction based on received args using Node's key to sign it -func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) { - // Look up the wallet containing the requested signer - if !b.Cfg.JSONRPC.AllowInsecureUnlock { - b.Logger.Debug("account unlock with HTTP access is forbidden") - return common.Hash{}, fmt.Errorf("account unlock with HTTP access is forbidden") - } - - _, err := b.ClientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.GetFrom().Bytes())) - if err != nil { - b.Logger.Error("failed to find key in keyring", "address", args.GetFrom(), "error", err.Error()) - return common.Hash{}, fmt.Errorf("failed to find key in the node's keyring; %s; %s", keystore.ErrNoMatch, err.Error()) - } - - if args.ChainID != nil && (b.EvmChainID).Cmp((*big.Int)(args.ChainID)) != 0 { - return common.Hash{}, fmt.Errorf("chainId does not match node's (have=%v, want=%v)", args.ChainID, (*hexutil.Big)(b.EvmChainID)) - } - - args, err = b.SetTxDefaults(args) - if err != nil { - return common.Hash{}, err - } - - bn, err := b.BlockNumber() - if err != nil { - b.Logger.Debug("failed to fetch latest block number", "error", err.Error()) - return common.Hash{}, err - } - - header, err := b.CurrentHeader() - if err != nil { - return common.Hash{}, err - } - - signer := ethtypes.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn)), header.Time) - - // LegacyTx derives EvmChainID from the signature. To make sure the msg.ValidateBasic makes - // the corresponding EvmChainID validation, we need to sign the transaction before calling it - - // Sign transaction - msg := evmtypes.NewTxFromArgs(&args) - if err := msg.Sign(signer, b.ClientCtx.Keyring); err != nil { - b.Logger.Debug("failed to sign tx", "error", err.Error()) - return common.Hash{}, err - } - - if err := msg.ValidateBasic(); err != nil { - b.Logger.Debug("tx failed basic validation", "error", err.Error()) - return common.Hash{}, err - } - - baseDenom := evmtypes.GetEVMCoinDenom() - - // Assemble transaction from fields - tx, err := msg.BuildTx(b.ClientCtx.TxConfig.NewTxBuilder(), baseDenom) - if err != nil { - b.Logger.Error("build cosmos tx failed", "error", err.Error()) - return common.Hash{}, err - } - - // Encode transaction by default Tx encoder - txEncoder := b.ClientCtx.TxConfig.TxEncoder() - txBytes, err := txEncoder(tx) - if err != nil { - b.Logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) - return common.Hash{}, err - } - - ethTx := msg.AsTransaction() - - // check the local node config in case unprotected txs are disabled - if !b.UnprotectedAllowed() && !ethTx.Protected() { - // Ensure only eip155 signed transactions are submitted if EIP155Required is set. - return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") - } - - txHash := ethTx.Hash() - - // Broadcast transaction in sync mode (default) - // NOTE: If error is encountered on the node, the broadcast will not return an error - syncCtx := b.ClientCtx.WithBroadcastMode(flags.BroadcastSync) - rsp, err := syncCtx.BroadcastTx(txBytes) - if rsp != nil && rsp.Code != 0 { - err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) - } - if err != nil { - // Check if this is a nonce gap error that was successfully queued - if b.Mempool != nil && strings.Contains(err.Error(), mempool.ErrNonceGap.Error()) { - // Transaction was successfully queued due to nonce gap, return success to client - b.Logger.Debug("transaction queued due to nonce gap", "hash", txHash.Hex()) - // Track as local for priority and persistence - b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) - return txHash, nil - } - // Temporary txpool rejections should be locally tracked for resubmission - if b.Mempool != nil && txlocals.IsTemporaryReject(err) { - b.Logger.Debug("temporary rejection, tracking locally", "hash", txHash.Hex(), "err", err.Error()) - b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) - return txHash, nil - } - b.Logger.Error("failed to broadcast tx", "error", err.Error()) - return txHash, err - } - - // Return transaction hash - // On success, track as local too to persist across restarts until mined - if b.Mempool != nil { - b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) - } - return txHash, nil -} - // Sign signs the provided data using the private key of address via Geth's signature standard. func (b *Backend) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { from := sdk.AccAddress(address.Bytes()) From bffcec6bfdeade763323425a6362386a8ec263ef Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 10:33:10 -0500 Subject: [PATCH 03/18] check both resp log and err --- rpc/backend/call_tx.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index 14f41227b..867d17c0d 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -18,7 +18,6 @@ import ( "google.golang.org/grpc/status" "github.com/cosmos/evm/mempool" - "github.com/cosmos/evm/mempool/txpool/locals" txlocals "github.com/cosmos/evm/mempool/txpool/locals" rpctypes "github.com/cosmos/evm/rpc/types" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -114,18 +113,19 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e if rsp != nil && rsp.Code != 0 { err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) } - if err != nil { - // Check if this is a nonce gap error that was successfully queued - if b.Mempool != nil && strings.Contains(err.Error(), mempool.ErrNonceGap.Error()) { - // Transaction was successfully queued due to nonce gap, return success to client - b.Logger.Debug("transaction queued due to nonce gap", "hash", txHash.Hex()) - // Track as local for priority and persistence + // Check for temporary rejection in response raw log + if b.Mempool != nil && rsp != nil && rsp.Code != 0 { + if txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { + b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) return txHash, nil } - // Temporary txpool rejections should be locally tracked for resubmission + err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) + } + if err != nil { + // Check for temporary rejection in error if b.Mempool != nil && txlocals.IsTemporaryReject(err) { - b.Logger.Debug("temporary rejection, tracking locally", "hash", txHash.Hex(), "err", err.Error()) + b.Logger.Debug("temporary rejection in error, tracking locally", "hash", txHash.Hex(), "err", err.Error()) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{ethTx}) return txHash, nil } @@ -192,19 +192,19 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { syncCtx := b.ClientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) + // Check for temporary rejection in response raw log if b.Mempool != nil && rsp != nil && rsp.Code != 0 { - if shouldSkip := locals.IsTemporaryReject(errors.New(rsp.RawLog)); shouldSkip { - b.Logger.Debug("temporary rejection, tracking locally", "tx_hash", txHash.Hex()) + if txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { + b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) + b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil } err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) } if err != nil { - // Check if this is a nonce gap error that was successfully queued - if b.Mempool != nil && strings.Contains(err.Error(), mempool.ErrNonceGap.Error()) { - // Transaction was successfully queued due to nonce gap, return success to client - b.Logger.Debug("transaction queued due to nonce gap", "hash", txHash.Hex()) - // Track as local for priority and persistence + // Check for temporary rejection in response raw log + if b.Mempool != nil && rsp != nil && rsp.Code != 0 && txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { + b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil } From efdbf2d99976dc6b39a7745cafc1276eed05cc80 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 10:43:08 -0500 Subject: [PATCH 04/18] fix string match case --- mempool/txpool/locals/errors.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mempool/txpool/locals/errors.go b/mempool/txpool/locals/errors.go index 641cbcfa4..3274bc2b6 100644 --- a/mempool/txpool/locals/errors.go +++ b/mempool/txpool/locals/errors.go @@ -2,7 +2,6 @@ package locals import ( "errors" - "strings" "github.com/cosmos/evm/mempool/txpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" @@ -18,14 +17,18 @@ var ( // transaction from being included in the txpool. The result may change if the txpool's state changes later. // We use strings.Contains instead of errors.Is because we are passing in rawLog errors. func IsTemporaryReject(err error) bool { - switch { - case strings.Contains(err.Error(), legacypool.ErrOutOfOrderTxFromDelegated.Error()): - case strings.Contains(err.Error(), txpool.ErrInflightTxLimitReached.Error()): - case strings.Contains(err.Error(), legacypool.ErrAuthorityReserved.Error()): - case strings.Contains(err.Error(), txpool.ErrUnderpriced.Error()): - case strings.Contains(err.Error(), legacypool.ErrTxPoolOverflow.Error()): - case strings.Contains(err.Error(), legacypool.ErrFutureReplacePending.Error()): - case strings.Contains(err.Error(), ErrNonceGap.Error()): + if err == nil { + return false + } + + switch err.Error() { + case legacypool.ErrOutOfOrderTxFromDelegated.Error(), + txpool.ErrInflightTxLimitReached.Error(), + legacypool.ErrAuthorityReserved.Error(), + txpool.ErrUnderpriced.Error(), + legacypool.ErrTxPoolOverflow.Error(), + legacypool.ErrFutureReplacePending.Error(), + ErrNonceGap.Error(): return true } From 7864f9de949b5a189650da4d10e1ed72d6622196 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 10:56:06 -0500 Subject: [PATCH 05/18] remove dup check --- rpc/backend/call_tx.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index 867d17c0d..a65c6fb9b 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -203,17 +203,11 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } if err != nil { // Check for temporary rejection in response raw log - if b.Mempool != nil && rsp != nil && rsp.Code != 0 && txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { + if b.Mempool != nil && txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil } - // Temporary txpool rejections should be locally tracked for resubmission - if b.Mempool != nil && txlocals.IsTemporaryReject(err) { - b.Logger.Debug("temporary rejection, tracking locally", "hash", txHash.Hex(), "err", err.Error()) - b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) - return txHash, nil - } if b.Mempool != nil && strings.Contains(err.Error(), mempool.ErrNonceLow.Error()) { from, err := ethSigner.Sender(tx) if err != nil { From b138e61be6b0bb9e067936e399c45a60886a1b08 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 11:00:07 -0500 Subject: [PATCH 06/18] expect error for last tx --- tests/systemtests/mempool/test_ordering.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index f92dfecf4..7f0550d05 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -36,7 +36,11 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - require.NoError(t, err, "failed to send tx") + if i < 4 { + require.NoError(t, err, "failed to send tx") + } else { + require.Error(t, err, "expected error for nonce gap") + } // nonce order of committed txs: 0,1,2,3,4 expPendingTxs[nonceIdx] = txInfo From ac052304b83f5932ca5273cd26d17d041e3fa004 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 11:04:08 -0500 Subject: [PATCH 07/18] filter by nonce idx --- tests/systemtests/mempool/test_ordering.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index 7f0550d05..6b02156b2 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -36,7 +36,7 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - if i < 4 { + if nonceIdx < 4 { require.NoError(t, err, "failed to send tx") } else { require.Error(t, err, "expected error for nonce gap") From 825117a78a243131bd078bfcca98caa06b939f7d Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 11:06:35 -0500 Subject: [PATCH 08/18] rev --- tests/systemtests/mempool/test_ordering.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index 6b02156b2..f92dfecf4 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -36,11 +36,7 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - if nonceIdx < 4 { - require.NoError(t, err, "failed to send tx") - } else { - require.Error(t, err, "expected error for nonce gap") - } + require.NoError(t, err, "failed to send tx") // nonce order of committed txs: 0,1,2,3,4 expPendingTxs[nonceIdx] = txInfo From ef631ab2d73b6f44c18e0eb8410388dfc6864cfd Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 11:27:40 -0500 Subject: [PATCH 09/18] expect error for all except --- tests/systemtests/mempool/test_ordering.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index f92dfecf4..d81b994ce 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -20,7 +20,8 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { name: "ordering of pending txs %s", actions: []func(*TestSuite, *TestContext){ func(s *TestSuite, ctx *TestContext) { - signer := s.Acc(0) + accIdx := 0 + signer := s.Acc(accIdx) expPendingTxs := make([]*suite.TxInfo, 5) for i := 0; i < 5; i++ { @@ -36,7 +37,11 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - require.NoError(t, err, "failed to send tx") + if i == accIdx { + require.NoError(t, err, "failed to send tx") + } else { + require.Error(t, err, "expected error for nonce gap") + } // nonce order of committed txs: 0,1,2,3,4 expPendingTxs[nonceIdx] = txInfo From 240b021f4db0e85718ace711164a53a443ee8bbc Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 11:30:52 -0500 Subject: [PATCH 10/18] use nonce gap --- tests/systemtests/mempool/test_ordering.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index d81b994ce..895d9cc93 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -37,7 +37,7 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - if i == accIdx { + if nonceIdx <= uint64(accIdx) { require.NoError(t, err, "failed to send tx") } else { require.Error(t, err, "expected error for nonce gap") From deafb4e9f9005417204071504d91611af6b11c2b Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 12:28:33 -0500 Subject: [PATCH 11/18] rev --- tests/systemtests/mempool/test_ordering.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/systemtests/mempool/test_ordering.go b/tests/systemtests/mempool/test_ordering.go index 895d9cc93..f92dfecf4 100644 --- a/tests/systemtests/mempool/test_ordering.go +++ b/tests/systemtests/mempool/test_ordering.go @@ -20,8 +20,7 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { name: "ordering of pending txs %s", actions: []func(*TestSuite, *TestContext){ func(s *TestSuite, ctx *TestContext) { - accIdx := 0 - signer := s.Acc(accIdx) + signer := s.Acc(0) expPendingTxs := make([]*suite.TxInfo, 5) for i := 0; i < 5; i++ { @@ -37,11 +36,7 @@ func RunTxsOrdering(t *testing.T, base *suite.BaseTestSuite) { } txInfo, err := s.SendTx(t, nodeId, signer.ID, nonceIdx, s.GasPriceMultiplier(10), big.NewInt(1)) - if nonceIdx <= uint64(accIdx) { - require.NoError(t, err, "failed to send tx") - } else { - require.Error(t, err, "expected error for nonce gap") - } + require.NoError(t, err, "failed to send tx") // nonce order of committed txs: 0,1,2,3,4 expPendingTxs[nonceIdx] = txInfo From 6c4a6b2541c972cbb9311fdbb90853d357a6dccd Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Wed, 5 Nov 2025 12:50:15 -0500 Subject: [PATCH 12/18] rm nonce gap --- mempool/txpool/locals/errors.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mempool/txpool/locals/errors.go b/mempool/txpool/locals/errors.go index 3274bc2b6..d201faed5 100644 --- a/mempool/txpool/locals/errors.go +++ b/mempool/txpool/locals/errors.go @@ -27,8 +27,7 @@ func IsTemporaryReject(err error) bool { legacypool.ErrAuthorityReserved.Error(), txpool.ErrUnderpriced.Error(), legacypool.ErrTxPoolOverflow.Error(), - legacypool.ErrFutureReplacePending.Error(), - ErrNonceGap.Error(): + legacypool.ErrFutureReplacePending.Error(): return true } From 26f02d32d9c340647b69a13582fe54bfde0c0be4 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 10:58:22 -0500 Subject: [PATCH 13/18] add back nonce gap case --- mempool/txpool/locals/errors.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mempool/txpool/locals/errors.go b/mempool/txpool/locals/errors.go index d201faed5..3274bc2b6 100644 --- a/mempool/txpool/locals/errors.go +++ b/mempool/txpool/locals/errors.go @@ -27,7 +27,8 @@ func IsTemporaryReject(err error) bool { legacypool.ErrAuthorityReserved.Error(), txpool.ErrUnderpriced.Error(), legacypool.ErrTxPoolOverflow.Error(), - legacypool.ErrFutureReplacePending.Error(): + legacypool.ErrFutureReplacePending.Error(), + ErrNonceGap.Error(): return true } From 25efd230bcf3479b48d778d0fa9b7884a1b6ccd9 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 11:01:13 -0500 Subject: [PATCH 14/18] fix wrong arg --- rpc/backend/call_tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index a65c6fb9b..ecac03780 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -203,7 +203,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } if err != nil { // Check for temporary rejection in response raw log - if b.Mempool != nil && txlocals.IsTemporaryReject(errors.New(rsp.RawLog)) { + if b.Mempool != nil && txlocals.IsTemporaryReject(err) { b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil From a68e02d36731f35e8856cea3f6935676b45479b9 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 11:08:44 -0500 Subject: [PATCH 15/18] add panic --- rpc/backend/call_tx.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index ecac03780..c60871233 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -204,7 +204,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { if err != nil { // Check for temporary rejection in response raw log if b.Mempool != nil && txlocals.IsTemporaryReject(err) { - b.Logger.Debug("temporary rejection in response raw log, tracking locally", "hash", txHash.Hex(), "err", rsp.RawLog) + b.Logger.Debug("temporary rejection in error, tracking locally", "hash", txHash.Hex(), "err", err.Error()) b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil } @@ -224,11 +224,13 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } // SendRawTransaction does not return error when committed nonce <= tx.Nonce < pending nonce - // Track as local for persistence until mined + // Track as local for persistence until pending b.Mempool.TrackLocalTxs([]*ethtypes.Transaction{tx}) return txHash, nil } + panic(fmt.Sprintf("------------------>>>> %s, %s, %v", err.Error(), txlocals.IsTemporaryReject(err), b.Mempool != nil)) + b.Logger.Error("failed to broadcast tx", "error", err.Error()) return txHash, fmt.Errorf("failed to broadcast transaction: %w", err) } From 7a3dc863c86b4c74fdebf0494203ef8d58863566 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 11:20:34 -0500 Subject: [PATCH 16/18] edit return --- rpc/backend/call_tx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index c60871233..08132fb5d 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -229,10 +229,10 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { return txHash, nil } - panic(fmt.Sprintf("------------------>>>> %s, %s, %v", err.Error(), txlocals.IsTemporaryReject(err), b.Mempool != nil)) + x := fmt.Sprintf("------------------>>>> %s, %s, %v", err.Error(), txlocals.IsTemporaryReject(err), b.Mempool != nil) b.Logger.Error("failed to broadcast tx", "error", err.Error()) - return txHash, fmt.Errorf("failed to broadcast transaction: %w", err) + return txHash, fmt.Errorf("failed to broadcast transaction: %s", x) } // On success, track as local too to persist across restarts until mined From 307fb074855c1964014c7438a2b2357999b6b98e Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 11:37:34 -0500 Subject: [PATCH 17/18] use contains instead of match --- mempool/txpool/locals/errors.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mempool/txpool/locals/errors.go b/mempool/txpool/locals/errors.go index 3274bc2b6..4e7d16209 100644 --- a/mempool/txpool/locals/errors.go +++ b/mempool/txpool/locals/errors.go @@ -2,6 +2,7 @@ package locals import ( "errors" + "strings" "github.com/cosmos/evm/mempool/txpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" @@ -21,14 +22,14 @@ func IsTemporaryReject(err error) bool { return false } - switch err.Error() { - case legacypool.ErrOutOfOrderTxFromDelegated.Error(), - txpool.ErrInflightTxLimitReached.Error(), - legacypool.ErrAuthorityReserved.Error(), - txpool.ErrUnderpriced.Error(), - legacypool.ErrTxPoolOverflow.Error(), - legacypool.ErrFutureReplacePending.Error(), - ErrNonceGap.Error(): + switch { + case strings.Contains(err.Error(), legacypool.ErrOutOfOrderTxFromDelegated.Error()), + strings.Contains(err.Error(), txpool.ErrInflightTxLimitReached.Error()), + strings.Contains(err.Error(), legacypool.ErrAuthorityReserved.Error()), + strings.Contains(err.Error(), txpool.ErrUnderpriced.Error()), + strings.Contains(err.Error(), legacypool.ErrTxPoolOverflow.Error()), + strings.Contains(err.Error(), legacypool.ErrFutureReplacePending.Error()), + strings.Contains(err.Error(), ErrNonceGap.Error()): return true } From 17cea3d197880530ddf5b9d3b60c9b2d09800517 Mon Sep 17 00:00:00 2001 From: Abdul Malek Date: Fri, 7 Nov 2025 11:43:39 -0500 Subject: [PATCH 18/18] fix system ordering test --- rpc/backend/call_tx.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index 08132fb5d..1c2d24275 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -229,10 +229,8 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { return txHash, nil } - x := fmt.Sprintf("------------------>>>> %s, %s, %v", err.Error(), txlocals.IsTemporaryReject(err), b.Mempool != nil) - b.Logger.Error("failed to broadcast tx", "error", err.Error()) - return txHash, fmt.Errorf("failed to broadcast transaction: %s", x) + return txHash, fmt.Errorf("failed to broadcast transaction: %w", err) } // On success, track as local too to persist across restarts until mined