Skip to content

execution/stagedsync: extract minIBS post-apply path into a dedicated method#21212

Merged
AskAlexSharov merged 1 commit into
mh/21138-drop-dead-finalizefrom
mh/21138-pr2-finalize-cleanup
May 19, 2026
Merged

execution/stagedsync: extract minIBS post-apply path into a dedicated method#21212
AskAlexSharov merged 1 commit into
mh/21138-drop-dead-finalizefrom
mh/21138-pr2-finalize-cleanup

Conversation

@mh0lt

@mh0lt mh0lt commented May 15, 2026

Copy link
Copy Markdown
Contributor

Stack

Depends on: #21211 — first cut of #21138's heuristic / IBS-dependency removal sequence (dead-finalize cleanup). Must merge first.

Summary

Pure structural cleanup of finalizeTxSimple. Pulls the ~55 lines that construct a minimal IntraBlockState to feed postApplyMessageFunc (AuRa system calls / EIP-7708 burn-log emission via LogSelfDestructedAccounts) into its own method, runPostApplyMessageOnMinIBS.

No semantic change. Behavior identical: same engine post-apply hook, same IBS construction, same control flow position.

Why

This is preparation for the IBS-removal PR that follows in the #21138 sequence. The end-state #21138 drives toward is one finalizeTx function, no IBS used in the parallel-exec code path outside workers. Today the live IBS dependency in finalizeTxSimple is entangled with surrounding fee-credit logic that has nothing to do with IBS — pulling them apart here, without changing behavior, makes the IBS-removal PR small enough to review confidently.

The extracted method's godoc enumerates the three IBS dependencies it carries today, so the swap point is clearly flagged:

  1. ibs.GetRemovedAccountsWithBalance() lookup (consumed inside LogSelfDestructedAccounts).
  2. ibs.AddLogibs.GetLogs log-buffer round-trip (so EIP-7708 burn logs reach the receipt).
  3. ibs.AddBalance fee-credit bookkeeping (so the SD'd coinbase carries FeeTipped at the time LogSelfDestructedAccounts inspects it).

finalizeTxSimple shrinks from 257 → 205 lines; the IBS-using block becomes a single method call.

Test plan

  • make lint clean
  • make test-short green under EXEC3_PARALLEL=true across: execution/stagedsync, execution/state, execution/tests, execution/engineapi, execution/execmodule, rpc/jsonrpc
  • TestEIP7708BurnLogWhenCoinbaseSelfDestructs green
  • TestEngineApiBAL* family green
  • TestLegacyBlockchain/ValidBlocks/bcEIP3675 green
  • CI: race-tests, kurtosis, hive matrix legs green on both serial and parallel

What's next in the sequence

  • PR 3 (next): drain all three IBS deps from runPostApplyMessageOnMinIBS. Add explicit SD-with-balance computation on ExecutionResult, change LogSelfDestructedAccounts to return []*types.Log instead of using ibs.AddLog, and remove the ibs.AddBalance bookkeeping (which exists only to inform LogSelfDestructedAccounts). finalizeTxSimple becomes IBS-free.
  • Later PRs: replace normalizeWriteSet with filterWritesByVersionMap; replace calcState.ApplyWrites with VersionedWrites.TouchUpdates; move EIP-7002/7251 syscalls into the worker pool (separate PR — requires interface change to Engine.Finalize / SysCallContract).

Related

… method

Pure structural cleanup of finalizeTxSimple — no semantic change. Pulls
the ~55 lines that construct a minimal IntraBlockState to feed
postApplyMessageFunc (AuRa system calls / EIP-7708 burn-log emission via
LogSelfDestructedAccounts) into its own method,
runPostApplyMessageOnMinIBS.

The extraction has two purposes:

* Names and isolates the load-bearing IBS dependency in
  finalizeTxSimple's post-execution path. The new godoc enumerates the
  three IBS deps the method carries today
  (GetRemovedAccountsWithBalance lookup, AddLog/GetLogs log buffering,
  AddBalance fee-credit bookkeeping) so the IBS-removal PR that
  follows has a clearly-flagged swap point.
* Shrinks finalizeTxSimple's body from 257 → 205 lines, removing the
  inline ibs.New / ApplyVersionedWrites / AddBalance / GetLogs
  sequence from the function the parallel-exec stack reads daily.

Behavior is identical: the new method runs the same engine
post-apply hook with the same IBS construction, in the same control
flow position. CollectorWrites / allWrites construction, fee-balance
math, receipt creation, and return values are untouched.

Verified under EXEC3_PARALLEL=true:

* make lint clean
* execution/stagedsync, execution/state, execution/tests,
  execution/engineapi, execution/execmodule, rpc/jsonrpc test-short
  green
* TestEIP7708BurnLogWhenCoinbaseSelfDestructs green
* TestEngineApiBAL family green
* TestLegacyBlockchain/ValidBlocks/bcEIP3675 green

Stacks on #21211. Sets up the IBS-removal PR in the #21138 sequence.
@AskAlexSharov AskAlexSharov merged commit e2b4094 into mh/21138-drop-dead-finalize May 19, 2026
2 checks passed
@AskAlexSharov AskAlexSharov deleted the mh/21138-pr2-finalize-cleanup branch May 19, 2026 01:45
@yperbasis

Copy link
Copy Markdown
Member

@mh0lt this PR targets some temporary branch, not main!

mh0lt added a commit that referenced this pull request May 23, 2026
…ectChange

journal.go defined two journal entry types for the same logical
"createObject" event with non-symmetric dirtied() returns:

    func (ch createObjectChange) dirtied() (accounts.Address, bool) { return ch.account, true }
    func (ch resetObjectChange)  dirtied() (accounts.Address, bool) { return accounts.NilAddress, false }

createObject in intra_block_state.go picks between them on
`previous == nil` — first-creation goes through createObjectChange,
recreation (e.g. SD-revival via GetOrNewStateObject) goes through
resetObjectChange. Both represent the same operation ("a stateObject
was placed at this address"); they differ only in revert behaviour.

The asymmetry bites parallel-exec when tx1 selfdestructs an address
and tx2 hits CreateAccount or GetOrNewStateObject on the same address:

  * Serial:   tx1's writer already DeleteAccount'd the addr, so
              getStateObject returns nil → createObject(addr, nil)
              appends createObjectChange → marks journal.dirties.
  * Parallel: versionedRead returns the contract's base-state account
              and reads tx1's SelfDestructPath=true; createObject
              synthesises a non-nil previous with selfdestructed=true
              → appends resetObjectChange → with the old return,
              does NOT mark journal.dirties.

At MakeWriteSet the worker IBS computes isDirty from journal.dirties.
With no mark, updateAccount falls through both DELETE and UPDATE
branches → LightCollector.UpdateAccountData never fires →
result.CollectorWrites is missing the empty-account write
(test_double_kill / EEST EIP-6780 family on the parallel shard).

Symmetric tracking restores the dirty mark for the recreate path
without changing first-create behaviour. Verified on
TestEngineApiBAL*, TestEIP7708BurnLog*, TestDeleteRecreate* under
EXEC3_PARALLEL=true.

## Known regression — see #21217

TestSelfDestructReceive fails under EXEC3_PARALLEL=true after this
change with a wrong-trie-root for block 1. The validator's
stateObject reconstruction for an SD-then-revived address emits
different field values (`nonce=1, codehash=emptyHash`) than the
unfixed canonical (`nonce=0, codehash=<nil>`). The empty-touch /
CreateAccount paths the fix addresses don't have this issue; the
AddBalance(non-zero) on an SD'd address does.

Filed as #21217 with full repro and the two failed narrow-fix
attempts (unconditional symmetry; conditional SD-revival +
SelfDestructPath=false re-emit). Lands as the last commit in this
stack so the broader structural direction is visible together; the
TestSelfDestructReceive regression is the explicit "more work
needed" marker before final merge.

Stacks on #21212.
mh0lt added a commit that referenced this pull request May 23, 2026
…ectChange

journal.go defined two journal entry types for the same logical
"createObject" event with non-symmetric dirtied() returns:

    func (ch createObjectChange) dirtied() (accounts.Address, bool) { return ch.account, true }
    func (ch resetObjectChange)  dirtied() (accounts.Address, bool) { return accounts.NilAddress, false }

createObject in intra_block_state.go picks between them on
`previous == nil` — first-creation goes through createObjectChange,
recreation (e.g. SD-revival via GetOrNewStateObject) goes through
resetObjectChange. Both represent the same operation ("a stateObject
was placed at this address"); they differ only in revert behaviour.

The asymmetry bites parallel-exec when tx1 selfdestructs an address
and tx2 hits CreateAccount or GetOrNewStateObject on the same address:

  * Serial:   tx1's writer already DeleteAccount'd the addr, so
              getStateObject returns nil → createObject(addr, nil)
              appends createObjectChange → marks journal.dirties.
  * Parallel: versionedRead returns the contract's base-state account
              and reads tx1's SelfDestructPath=true; createObject
              synthesises a non-nil previous with selfdestructed=true
              → appends resetObjectChange → with the old return,
              does NOT mark journal.dirties.

At MakeWriteSet the worker IBS computes isDirty from journal.dirties.
With no mark, updateAccount falls through both DELETE and UPDATE
branches → LightCollector.UpdateAccountData never fires →
result.CollectorWrites is missing the empty-account write
(test_double_kill / EEST EIP-6780 family on the parallel shard).

Symmetric tracking restores the dirty mark for the recreate path
without changing first-create behaviour. Verified on
TestEngineApiBAL*, TestEIP7708BurnLog*, TestDeleteRecreate* under
EXEC3_PARALLEL=true.

## Known regression — see #21217

TestSelfDestructReceive fails under EXEC3_PARALLEL=true after this
change with a wrong-trie-root for block 1. The validator's
stateObject reconstruction for an SD-then-revived address emits
different field values (`nonce=1, codehash=emptyHash`) than the
unfixed canonical (`nonce=0, codehash=<nil>`). The empty-touch /
CreateAccount paths the fix addresses don't have this issue; the
AddBalance(non-zero) on an SD'd address does.

Filed as #21217 with full repro and the two failed narrow-fix
attempts (unconditional symmetry; conditional SD-revival +
SelfDestructPath=false re-emit). Lands as the last commit in this
stack so the broader structural direction is visible together; the
TestSelfDestructReceive regression is the explicit "more work
needed" marker before final merge.

Stacks on #21212.
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.

3 participants