Skip to content

[BUG] NonceManager.refresh() bypasses the per-account lock, reopening sequence-collision races during fee-bump/manual-tx recovery #183

Description

@N-thnI

Priority: High

Description

NonceManager documents itself as serializing reservation per-account via this.locks, and reserve() correctly waits on/sets the lock. refresh() — meant to force a fresh read "after fee-bump or manual tx" — never touches this.locks and can run concurrently with an in-flight reserve() for the same account, corrupting the cache.

Location

engine-bridge/src/nonce-manager.ts:49-53 (refresh), racing with :24-39 (reserve)

Current Behavior

async refresh(accountId: string): Promise<void> {
  this.cache.delete(accountId);
  await this.nextSequence(accountId); // no lock at all
}

Expected Behavior

refresh() should participate in the same per-account lock as reserve() so the two can never interleave.

Repro / Evidence

  1. reserve("G...") enters nextSequence, suspends on network I/O, holds no cache entry yet.
  2. refresh("G...") runs concurrently, does its own getAccount round-trip, writes cache.
  3. Original reserve()'s call resolves and unconditionally overwrites the cache, discarding refresh()'s value and returning a stale/colliding sequence.

Impact

Reopens exactly the sequence-collision hazard NonceManager exists to prevent, specifically on the recovery path an operator invokes when something has already gone wrong — compounding an incident.

Suggested Fix

Route refresh() through the same lock acquisition as reserve() (factor lock wait/acquire/release into a shared private helper).

Acceptance Criteria

  • AC-1: refresh() acquires the per-account lock before mutating cache.
  • AC-2: Concurrent reserve()/refresh() for the same account can't interleave.
  • AC-3: New test with a deliberately delayed getAccount during reserve(), calling refresh() mid-flight — fails on current code.

Definition of Done

  • Fix merged with all AC items checked
  • Regression test passes in CI
  • No new eslint warnings

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignbugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions