Skip to content

getPendingCount() Redis Error Handling — Divergent Behavior by Caller #4934

@simzzz

Description

@simzzz

Problem

TransactionPoolService.getPendingCount() is called from two places with fundamentally different availability/correctness requirements:

  1. Precheck.sendRawTransactionCheck() — uses the count to compute the expected nonce before submission. If Redis is down, returning 0 is acceptable: the consensus node will catch any nonce mismatch, and the user gets a meaningful error.

  2. AccountService.getTransactionCount() (eth_getTransactionCount('pending')) — returns the pending nonce directly to wallets. Returning 0 here is harmful: the wallet will build a transaction with a stale nonce and enter a confusing retry loop (NONCE_TOO_LOW / replacement).

Currently the method either always throws (blocking submission entirely) or always returns 0 (giving wallets wrong data). Neither is correct for both callers.

Solution

Add a fallbackToZero flag to getPendingCount(address, fallbackToZero = false).

  • When fallbackToZero = true (used by sendRawTransactionCheck): Redis errors are caught internally, a warning is logged, and 0 is returned — transactions continue through to consensus which validates the actual nonce.
  • When fallbackToZero = false (used by getTransactionCount, the default): errors propagate as before — the wallet receives an explicit failure rather than a wrong nonce.
async getPendingCount(address: string, fallbackToZero = false): Promise<number> {
  if (!TransactionPoolService.isEnabled()) return 0;

  try {
    return await this.storage.getList(address.toLowerCase());
  } catch (error) {
    if (fallbackToZero) {
      this.logger.warn(error, 'Redis unavailable for getPendingCount, falling back to 0');
      return 0;
    }
    throw error;
  }
}

Call sites:

// Precheck — graceful degradation, consensus validates nonce
await this.transactionPoolService.getPendingCount(parsedTx.from!, true);

// getTransactionCount — fail fast, wallet must not receive a wrong nonce
await this.transactionPoolService.getPendingCount(address);

This keeps error handling centralised inside the service, preserves function purity at the default, and makes the caller's intent explicit without scattering try/catch blocks across the codebase.

Alternatives

No response

Metadata

Metadata

Assignees

Labels

Team PromotedIssues to be triaged and discussed by committers, maintainers, to be worked on the following sprintenhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions