Skip to content

[SharovBot] fix(txpool): inject mined-tx senders into state diff to evict stale pending txns (AuRa/Gnosis)#19592

Open
Giulio2002 wants to merge 3 commits intomainfrom
fix/txpool-aura-nonce-state-diff
Open

[SharovBot] fix(txpool): inject mined-tx senders into state diff to evict stale pending txns (AuRa/Gnosis)#19592
Giulio2002 wants to merge 3 commits intomainfrom
fix/txpool-aura-nonce-state-diff

Conversation

@Giulio2002
Copy link
Collaborator

@Giulio2002 Giulio2002 commented Mar 3, 2026

[SharovBot]

This is the AuRa stale-pending fix (first half of the split requested by @yperbasis on #19393). The zombie-queued fix (second half) was addressed in #19449 (merged).

Implements the EL-level fix suggested by @yperbasis: make the execution layer emit state-diff UPSERT entries for system-transaction senders so the txpool evicts stale pending transactions naturally.

Root Cause

In exec3_serial.go, the block-end stateWriter is constructed with a nil accumulator:

stateWriter := state.NewWriter(se.doms.AsPutDel(se.applyTx), nil, txTask.TxNum)

This means that engine.Finalize / engine.FinalizeAndAssemble — which execute AuRa system calls (validator rewards, bridge calls, etc.) — write account changes to the DB but do not emit UPSERT entries into the accumulator. The accumulator batch is what gets forwarded to the txpool as stateChanges, so the txpool never learns that those senders' nonces advanced.

As a result:

  1. The sender's nonce is mined to N+1 on-chain by a system tx
  2. The sender does not appear in stateChanges.ChangeBatch
  3. onSenderStateChange is never called for them
  4. Their pooled pending transactions (nonce ≤ N) remain in the pending pool forever
  5. Block builders attempt to include these, receive nonce too low, and produce empty or near-empty blocks

Observed on Gnosis Chain validator nodes.

Fix

Store the block accumulator on serialExecutor and pass it to the block-end stateWriter:

// exec3_serial.go — block loop, per block:
se.accumulator = accumulator          // set alongside StartChange

// block-end stateWriter (was: nil accumulator):
stateWriter := state.NewWriter(se.doms.AsPutDel(se.applyTx), se.accumulator, txTask.TxNum)

Now UpdateAccountDataChangeAccount calls during Finalize emit UPSERT entries into the batch. The txpool receives the correct stateChanges and onSenderStateChange fires for system-tx senders, evicting stale pending transactions.

Changes

  • execution/stagedsync/exec3_serial.go: add accumulator field to serialExecutor; set it per block; pass it to the block-end stateWriter
  • txnprovider/txpool/pool.go: no txpool changes (previous workaround removed)
  • txnprovider/txpool/pool_test.go: TestStalePendingEvictionViaMineNonce regression test

Testing

go test ./txnprovider/txpool/... -run TestStalePendingEvictionViaMineNonce -v  # PASS
go test ./txnprovider/txpool/... -count=1                                       # all PASS
go test ./execution/stagedsync/... -count=1                                     # all PASS

Related

Copy link
Member

@yperbasis yperbasis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to be fixed differently, namely for the the execution layer to emit state-diff UPSERT entries with the nonce changes for system transactions.

@Giulio2002 Giulio2002 force-pushed the fix/txpool-aura-nonce-state-diff branch from 5805d41 to 91b8250 Compare March 4, 2026 09:54
@Giulio2002 Giulio2002 requested a review from mh0lt as a code owner March 4, 2026 09:54
@Giulio2002
Copy link
Collaborator Author

[SharovBot]

Done, @yperbasis — updated to fix this at the execution layer as you suggested.

Root cause: in exec3_serial.go, the block-end stateWriter was constructed with nil accumulator:

stateWriter := state.NewWriter(se.doms.AsPutDel(se.applyTx), nil, txTask.TxNum)

This meant that engine.Finalize/engine.FinalizeAndAssemble (where AuRa system calls run) wrote account changes to the DB but never emitted them into the accumulator batch that gets forwarded to the txpool.

Fix: store the accumulator on serialExecutor and pass it to the block-end stateWriter so UpdateAccountData → ChangeAccount calls during Finalize emit the correct UPSERT entries.

The txpool workaround (ensureMinedSendersInStateDiff) has been removed entirely.

Copy link
Member

@yperbasis yperbasis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you check exec3_parallel?

… senders to evict stale pending txns

On AuRa/Gnosis Chain, block finalization (engine.Finalize) executes system
transactions (validator rewards, bridge calls, etc.) that advance sender
nonces. These state changes were NOT reaching the txpool state-diff batch
because the block-end stateWriter in exec3_serial.go was constructed with a
nil accumulator.

Fix: store the block's accumulator on serialExecutor (se.accumulator) and
pass it to the block-end stateWriter so that UpdateAccountData → ChangeAccount
calls during Finalize/FinalizeAndAssemble emit UPSERT entries into the batch.
The txpool then calls onSenderStateChange for those senders and evicts any
pending transactions whose nonces are now stale.

Changes:
- execution/stagedsync/exec3_serial.go:
  - Add accumulator field to serialExecutor
  - Set se.accumulator = accumulator alongside StartChange per block
  - Pass se.accumulator to state.NewWriter in the block-end path
- txnprovider/txpool/pool.go:
  - Remove txpool-level ensureMinedSendersInStateDiff workaround
  - Restore original cache.OnNewBlock ordering
- txnprovider/txpool/pool_test.go:
  - Update TestStalePendingEvictionViaMineNonce to test via correct
    stateChanges from the EL (as now emitted after this fix)

This is the EL-level fix requested by @yperbasis on #19392/#19393.
@Giulio2002 Giulio2002 force-pushed the fix/txpool-aura-nonce-state-diff branch from 91b8250 to d4e35f9 Compare March 4, 2026 10:16
@Giulio2002
Copy link
Collaborator Author

[SharovBot]

Yes, @yperbasis — applied the same fix to exec3_parallel.go. Both state.NewBufferedWriter(pe.rs, nil) calls at the block finalizer (lines 698 and 1562) now pass the accumulator instead of nil.

Added accumulator *shards.Accumulator field to parallelExecutor, stored at the top of pe.exec(), and referenced as pe.accumulator in blockExecutor.nextResult() (which doesn't have the parameter in scope directly).

Also fixed the lint error: added defer tx.Rollback() in the test's writeAccount helper.

@lystopad lystopad enabled auto-merge (squash) March 5, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants