Skip to content

Commit e2b4094

Browse files
authored
execution/stagedsync: extract minIBS post-apply path into a dedicated method (#21212)
## Stack **Depends on:** #21211 — first cut of [#21138](#21138 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](#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.AddLog` → `ibs.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 - [x] `make lint` clean - [x] `make test-short` green under `EXEC3_PARALLEL=true` across: `execution/stagedsync`, `execution/state`, `execution/tests`, `execution/engineapi`, `execution/execmodule`, `rpc/jsonrpc` - [x] `TestEIP7708BurnLogWhenCoinbaseSelfDestructs` green - [x] `TestEngineApiBAL*` family green - [x] `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 - #21211#21138 PR 1 (dead-finalize cleanup, this PR's direct dependency) - #21138 — heuristic / IBS-dependency removal tracker (the parent) - #21153 — parallel-exec correctness stack (merged 2026-05-15)
1 parent 1522edd commit e2b4094

1 file changed

Lines changed: 100 additions & 55 deletions

File tree

execution/stagedsync/exec3_parallel.go

Lines changed: 100 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,61 +1700,9 @@ func (result *execResult) finalizeTxSimple(
17001700
}
17011701
}
17021702

1703-
// Engine post-apply message (e.g., AuRa system calls).
1704-
if engine != nil {
1705-
if postApplyMessageFunc := engine.GetPostApplyMessageFunc(); postApplyMessageFunc != nil {
1706-
execResult := result.ExecutionResult
1707-
cbReader := state.NewVersionedStateReader(txIndex, nil, vm, stateReader)
1708-
coinbase, err := cbReader.ReadAccountData(result.Coinbase)
1709-
if err != nil {
1710-
return nil, nil, nil, err
1711-
}
1712-
if coinbase != nil {
1713-
execResult.CoinbaseInitBalance = coinbase.Balance
1714-
}
1715-
message, err := task.TxMessage()
1716-
if err != nil {
1717-
return nil, nil, nil, err
1718-
}
1719-
// PostApplyMessage needs an IBS — create a minimal one
1720-
ibs := state.New(state.NewVersionedStateReader(txIndex, result.TxIn, vm, stateReader))
1721-
ibs.SetTxContext(blockNum, txIndex)
1722-
if err := ibs.ApplyVersionedWrites(result.TxOut); err != nil {
1723-
return nil, nil, nil, err
1724-
}
1725-
// Mirror serial-exec's txn_executor.go post-message fee distribution
1726-
// (AddBalance(coinbase, tip) / AddBalance(burnt, burn)) AFTER applying
1727-
// TxOut. The parallel worker ran with shouldDelayFeeCalc=true, so the
1728-
// fees aren't in TxOut; the finalize accumulates them onto the
1729-
// version-map base separately. But the post-apply IBS handed to
1730-
// postApplyMessageFunc only sees TxOut — without crediting the fees
1731-
// here it underrepresents the coinbase's balance to LogSelfDestructedAccounts.
1732-
//
1733-
// The EIP-7708 case 2 path is the load-bearing one: when the coinbase
1734-
// is itself a contract that SELFDESTRUCTs during the tx, ApplyVersionedWrites
1735-
// has marked it selfdestructed with balance=0; AddBalance leaves the
1736-
// selfdestruct flag intact (Selfdestruct only fires on the addr→clear
1737-
// transition, not on subsequent balance writes) and restores the priority
1738-
// fee as residual balance. LogSelfDestructedAccounts' GetRemovedAccountsWithBalance
1739-
// then reports {coinbase, FeeTipped} and emits the Burn log — matching
1740-
// serial-exec exactly. https://github.com/erigontech/erigon/issues/21136
1741-
if err := ibs.AddBalance(result.Coinbase, result.ExecutionResult.FeeTipped, tracing.BalanceIncreaseRewardTransactionFee); err != nil {
1742-
return nil, nil, nil, err
1743-
}
1744-
if hasBurnt && txTask.Config.IsLondon(blockNum) {
1745-
if err := ibs.AddBalance(burntAddr, result.ExecutionResult.FeeBurnt, tracing.BalanceDecreaseGasBuy); err != nil {
1746-
return nil, nil, nil, err
1747-
}
1748-
}
1749-
postApplyMessageFunc(ibs, message.From(), result.Coinbase, &execResult, chainRules)
1750-
1751-
// Capture PostApplyMessage side effects (logs) — e.g. EIP-7708 Burn
1752-
// logs from LogSelfDestructedAccounts. Without this they're stranded
1753-
// on the post-apply ibs and never make it into the receipt, so the
1754-
// validating consumer recomputes a different receipts root and the
1755-
// block is rejected as a BadBlock.
1756-
result.Logs = append(result.Logs, ibs.GetLogs(txTask.TxIndex, txTask.TxHash(), blockNum, txTask.BlockHash())...)
1757-
}
1703+
// Engine post-apply message (e.g., AuRa system calls, EIP-7708 burn logs).
1704+
if err := result.runPostApplyMessageOnMinIBS(task, txTask, engine, vm, stateReader, hasBurnt, burntAddr); err != nil {
1705+
return nil, nil, nil, err
17581706
}
17591707

17601708
// Compute receipt.
@@ -1771,6 +1719,103 @@ func (result *execResult) finalizeTxSimple(
17711719
return receipt, nil, allWrites, nil
17721720
}
17731721

1722+
// runPostApplyMessageOnMinIBS runs the engine's PostApplyMessage callback
1723+
// (e.g. AuRa system calls, EIP-7708 burn-log emission via
1724+
// LogSelfDestructedAccounts) and appends any resulting logs to result.Logs.
1725+
//
1726+
// This is the load-bearing IntraBlockState use in finalizeTxSimple's
1727+
// post-execution path. It exists to:
1728+
//
1729+
// 1. Read SD'd accounts and their residual balances
1730+
// (ibs.GetRemovedAccountsWithBalance) so LogSelfDestructedAccounts can
1731+
// emit EIP-7708 burn logs.
1732+
// 2. Provide a log buffer (ibs.AddLog → ibs.GetLogs) so logs emitted by
1733+
// postApplyMessageFunc reach the receipt.
1734+
// 3. Run AddBalance bookkeeping for the priority-fee credit so the SD'd
1735+
// coinbase carries FeeTipped at the time LogSelfDestructedAccounts
1736+
// inspects it.
1737+
//
1738+
// All three dependencies are slated for removal under #21138 — once the
1739+
// SD-with-balance signal is explicit on ExecutionResult and
1740+
// LogSelfDestructedAccounts returns logs as a value, this method becomes
1741+
// IBS-free and the minimal IBS construction below disappears.
1742+
func (result *execResult) runPostApplyMessageOnMinIBS(
1743+
task *taskVersion,
1744+
txTask *exec.TxTask,
1745+
engine rules.Engine,
1746+
vm *state.VersionMap,
1747+
stateReader state.StateReader,
1748+
hasBurnt bool,
1749+
burntAddr accounts.Address,
1750+
) error {
1751+
if engine == nil {
1752+
return nil
1753+
}
1754+
postApplyMessageFunc := engine.GetPostApplyMessageFunc()
1755+
if postApplyMessageFunc == nil {
1756+
return nil
1757+
}
1758+
1759+
blockNum := task.Version().BlockNum
1760+
txIndex := task.Version().TxIndex
1761+
chainRules := txTask.EvmBlockContext.Rules(txTask.Config)
1762+
1763+
execResult := result.ExecutionResult
1764+
cbReader := state.NewVersionedStateReader(txIndex, nil, vm, stateReader)
1765+
coinbase, err := cbReader.ReadAccountData(result.Coinbase)
1766+
if err != nil {
1767+
return err
1768+
}
1769+
if coinbase != nil {
1770+
execResult.CoinbaseInitBalance = coinbase.Balance
1771+
}
1772+
message, err := task.TxMessage()
1773+
if err != nil {
1774+
return err
1775+
}
1776+
1777+
// PostApplyMessage needs an IBS — create a minimal one.
1778+
ibs := state.New(state.NewVersionedStateReader(txIndex, result.TxIn, vm, stateReader))
1779+
ibs.SetTxContext(blockNum, txIndex)
1780+
if err := ibs.ApplyVersionedWrites(result.TxOut); err != nil {
1781+
return err
1782+
}
1783+
1784+
// Mirror serial-exec's txn_executor.go post-message fee distribution
1785+
// (AddBalance(coinbase, tip) / AddBalance(burnt, burn)) AFTER applying
1786+
// TxOut. The parallel worker ran with shouldDelayFeeCalc=true, so the
1787+
// fees aren't in TxOut; the finalize accumulates them onto the
1788+
// version-map base separately. But the post-apply IBS handed to
1789+
// postApplyMessageFunc only sees TxOut — without crediting the fees
1790+
// here it underrepresents the coinbase's balance to LogSelfDestructedAccounts.
1791+
//
1792+
// The EIP-7708 case 2 path is the load-bearing one: when the coinbase
1793+
// is itself a contract that SELFDESTRUCTs during the tx, ApplyVersionedWrites
1794+
// has marked it selfdestructed with balance=0; AddBalance leaves the
1795+
// selfdestruct flag intact (Selfdestruct only fires on the addr→clear
1796+
// transition, not on subsequent balance writes) and restores the priority
1797+
// fee as residual balance. LogSelfDestructedAccounts' GetRemovedAccountsWithBalance
1798+
// then reports {coinbase, FeeTipped} and emits the Burn log — matching
1799+
// serial-exec exactly. https://github.com/erigontech/erigon/issues/21136
1800+
if err := ibs.AddBalance(result.Coinbase, result.ExecutionResult.FeeTipped, tracing.BalanceIncreaseRewardTransactionFee); err != nil {
1801+
return err
1802+
}
1803+
if hasBurnt && txTask.Config.IsLondon(blockNum) {
1804+
if err := ibs.AddBalance(burntAddr, result.ExecutionResult.FeeBurnt, tracing.BalanceDecreaseGasBuy); err != nil {
1805+
return err
1806+
}
1807+
}
1808+
postApplyMessageFunc(ibs, message.From(), result.Coinbase, &execResult, chainRules)
1809+
1810+
// Capture PostApplyMessage side effects (logs) — e.g. EIP-7708 Burn
1811+
// logs from LogSelfDestructedAccounts. Without this they're stranded
1812+
// on the post-apply ibs and never make it into the receipt, so the
1813+
// validating consumer recomputes a different receipts root and the
1814+
// block is rejected as a BadBlock.
1815+
result.Logs = append(result.Logs, ibs.GetLogs(txTask.TxIndex, txTask.TxHash(), blockNum, txTask.BlockHash())...)
1816+
return nil
1817+
}
1818+
17741819
type taskVersion struct {
17751820
*execTask
17761821
version state.Version

0 commit comments

Comments
 (0)