From 58fad516dca130e3f6a211eada9f04c0335df6c9 Mon Sep 17 00:00:00 2001 From: jeevan-sid Date: Mon, 16 Feb 2026 19:03:31 +0530 Subject: [PATCH] feat: implement getTransactionByNonceAndSender --- eth/api_backend.go | 27 +++++++++++++++++++++++++++ ethclient/ethclient.go | 10 ++++++++++ internal/ethapi/api.go | 9 +++++++++ internal/ethapi/backend.go | 1 + 4 files changed, 47 insertions(+) diff --git a/eth/api_backend.go b/eth/api_backend.go index 3f826b7861..2594cdc925 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -215,6 +215,33 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r return nil, errors.New("invalid arguments; neither block nor hash specified") } +// GetTransactionBySenderAndNonce returns the hash of a transaction for the given sender and nonce. +// It checks the pool, then enforces a nonce check against the current state, and finally checks the historical TxSenderNonce index. +func (b *EthAPIBackend) GetTransactionBySenderAndNonce(ctx context.Context, sender common.Address, nonce uint64) (*common.Hash, error) { + + if pool := b.eth.TxPool(); pool != nil { + if tx := pool.GetTxBySenderAndNonce(sender, nonce); tx != nil { + hash := tx.Hash() + return &hash, nil + } + } + + state, _, err := b.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) + if err != nil { + return nil, err + } + currentNonce := state.GetNonce(sender) + + // If the requested nonce is greater than or equal to the current nonce, + // and we already confirmed it's NOT in the pool, then the transaction + // definitely does not exist. + if nonce >= currentNonce { + return nil, nil + } + hash := rawdb.ReadTxSenderNonceEntry(b.eth.ChainDb(), sender, nonce) + return hash, nil +} + func (b *EthAPIBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) { return b.eth.miner.Pending() } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index bc4eaad6fa..967fd7e58e 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -288,6 +288,16 @@ func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx * return json.tx, json.BlockNumber == nil, nil } +// TransactionHashBySenderAndNonce returns the transaction hash for the given sender and nonce. +func (ec *Client) TransactionHashBySenderAndNonce(ctx context.Context, sender common.Address, nonce uint64) (*common.Hash, error) { + var hash *common.Hash + err := ec.c.CallContext(ctx, &hash, "eth_getTransactionBySenderAndNonce", sender, nonce) + if err != nil { + return nil, err + } + return hash, nil +} + // TransactionSender returns the sender address of the given transaction. The transaction // must be known to the remote node and included in the blockchain at the given block and // index. The sender is the one derived by the protocol at the time of inclusion. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5fbe7db694..b034badcd1 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1449,6 +1449,15 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, api.b.ChainConfig()), nil } +// GetTransactionBySenderAndNonce returns the hash of a transaction for the given sender and nonce. +func (s *TransactionAPI) GetTransactionBySenderAndNonce(ctx context.Context, sender common.Address, nonce uint64) (*common.Hash, error) { + hash, err := s.b.GetTransactionBySenderAndNonce(ctx, sender, nonce) + if err != nil { + return nil, err + } + return hash, nil +} + // GetRawTransactionByHash returns the bytes of the transaction for the given hash. func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { // Retrieve a finalized transaction, or a pooled otherwise diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index af3d592b82..754a771ab3 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -66,6 +66,7 @@ type Backend interface { BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) + GetTransactionBySenderAndNonce(ctx context.Context, sender common.Address, nonce uint64) (*common.Hash, error) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) Pending() (*types.Block, types.Receipts, *state.StateDB)