#2692 removes a timing-dependent assertion from the contract deployment integration test in file eth/rpc/rpcapi/api_eth_test.go. That test used to assume a transaction disappears from eth_getPendingTransactions after a single block increment. In practice, BroadcastSync only guarantees CheckTx admission—it does not define when the mempool drops a transaction or when the RPC layer stops listing it as pending.
The contract test still validates that a confirmed deployment is absent from the pending set, but only after receipt confirmation via helper function waitForEthReceipt. That keeps the deployment scenario stable without encoding mempool timing assumptions into unrelated assertions.
This issue tracks the gap that remains: we lack dedicated coverage for pending-transaction lifecycle semantics on their own.
What the RPC layer does today
Method GetPendingTransactions on struct EthAPI in file eth/rpc/rpcapi/api_eth.go serves eth_getPendingTransactions. It delegates to method PendingTransactions on struct Backend in file eth/rpc/rpcapi/chain_info.go, which reads unconfirmed transactions from the CometBFT mempool via RPC UnconfirmedTxs and decodes each into a Cosmos sdk.Tx. Only messages of type evm.MsgEthereumTx are surfaced as Ethereum JSON-RPC transactions.
File eth/rpc/rpcapi/chain_info_test.go already has a positive case: broadcast without waiting for a block, then assert the transaction appears in the pending list. What we do not have is explicit, synchronized coverage for the transition from pending to confirmed—or for how long a transaction may linger in the pending query after inclusion.
Why this matters
Wallets, indexers, and WebSocket subscribers (eth_newPendingTransactions, pending filters in file eth/rpc/rpcapi/api_eth_filters.go) depend on predictable pending semantics. If a client polls eth_getPendingTransactions immediately after broadcast, or one block after broadcast but before receipt availability, the answer may still include the transaction even though execution has already committed. Tests that conflate "one block passed" with "no longer pending" will flake under load or on slower localnets.
Separating lifecycle coverage from contract-deployment tests keeps each test focused: deployment tests assert contract behavior; pending tests assert RPC/mempool behavior.
Desired coverage (behavioral, not implementation)
Add integration or RPC-level tests that treat pending lifecycle as the subject under test:
- Establish expected
eth_getPendingTransactions behavior while a transaction is genuinely unconfirmed (still in mempool, no receipt).
- Establish expected behavior after receipt confirmation—the transaction must not appear in the pending list.
- Synchronize on receipt availability (polling
eth_getTransactionReceipt or an equivalent explicit signal) rather than assuming one block increment implies mempool removal.
- On failure, log transaction hash, current block height, pending count, and whether a receipt exists—enough context to distinguish "still in mempool" from "RPC bug."
- Keep this suite independent of contract-deployment assertions in
Test_SmartContract.
Related
#2692 removes a timing-dependent assertion from the contract deployment integration test in file
eth/rpc/rpcapi/api_eth_test.go. That test used to assume a transaction disappears frometh_getPendingTransactionsafter a single block increment. In practice,BroadcastSynconly guarantees CheckTx admission—it does not define when the mempool drops a transaction or when the RPC layer stops listing it as pending.The contract test still validates that a confirmed deployment is absent from the pending set, but only after receipt confirmation via helper function
waitForEthReceipt. That keeps the deployment scenario stable without encoding mempool timing assumptions into unrelated assertions.This issue tracks the gap that remains: we lack dedicated coverage for pending-transaction lifecycle semantics on their own.
What the RPC layer does today
Method
GetPendingTransactionson structEthAPIin fileeth/rpc/rpcapi/api_eth.goserveseth_getPendingTransactions. It delegates to methodPendingTransactionson structBackendin fileeth/rpc/rpcapi/chain_info.go, which reads unconfirmed transactions from the CometBFT mempool via RPCUnconfirmedTxsand decodes each into a Cosmossdk.Tx. Only messages of typeevm.MsgEthereumTxare surfaced as Ethereum JSON-RPC transactions.File
eth/rpc/rpcapi/chain_info_test.goalready has a positive case: broadcast without waiting for a block, then assert the transaction appears in the pending list. What we do not have is explicit, synchronized coverage for the transition from pending to confirmed—or for how long a transaction may linger in the pending query after inclusion.Why this matters
Wallets, indexers, and WebSocket subscribers (
eth_newPendingTransactions, pending filters in fileeth/rpc/rpcapi/api_eth_filters.go) depend on predictable pending semantics. If a client pollseth_getPendingTransactionsimmediately after broadcast, or one block after broadcast but before receipt availability, the answer may still include the transaction even though execution has already committed. Tests that conflate "one block passed" with "no longer pending" will flake under load or on slower localnets.Separating lifecycle coverage from contract-deployment tests keeps each test focused: deployment tests assert contract behavior; pending tests assert RPC/mempool behavior.
Desired coverage (behavioral, not implementation)
Add integration or RPC-level tests that treat pending lifecycle as the subject under test:
eth_getPendingTransactionsbehavior while a transaction is genuinely unconfirmed (still in mempool, no receipt).eth_getTransactionReceiptor an equivalent explicit signal) rather than assuming one block increment implies mempool removal.Test_SmartContract.Related