Background
MiningStep (in execution/stagedsync/stageloop/stageloop.go) builds a new block by running the builder stage pipeline. It has historically opened a BeginTemporalRw (MDBX exclusive write transaction) as the base tx, wrapped it in a MemoryBatch overlay, and always rolled back the base tx when done. The write transaction was never needed — all actual writes from the mining stages go into the MemoryBatch in-memory buffer, which is discarded without flushing.
PR #19821 fixed the immediate symptom — switching BeginTemporalRw to BeginTemporalRo to stop holding the MDBX exclusive write lock during block builds. However, MemoryBatch is still present purely as a compatibility shim to satisfy the kv.TemporalRwTx type required by Sync.Run and ExecFunc.
Why MemoryBatch is now unnecessary
The builder stage pipeline (builderstages) was refactored so that all state writes go through SharedDomains, not through the underlying kv.TemporalRwTx. Inspection of the builder stages confirms:
SpawnBuilderCreateBlockStage — only reads from tx (rawdb.ReadHeaderByNumber, rawdb.ReadHeadersByNumber)
SpawnBuilderExecStage — only reads from tx (rawdb.ReadHeader)
StageExecuteBlocksCfg (forward path) — writes state exclusively through SharedDomains
No builder stage writes directly to tx. MemoryBatch exists only to provide a kv.TemporalRwTx-shaped object to Sync.Run.
Proposed fix
Broaden the interfaces in the stage pipeline so the builder path can use a plain read-only tx:
ExecFunc / UnwindFunc (execution/stagedsync/stage.go): change rwTx kv.TemporalRwTx → tx kv.TemporalTx
Sync.Run, Sync.RunUnwind, Sync.RunNoInterrupt, Sync.RunSnapshots (execution/stagedsync/sync.go): change tx kv.TemporalRwTx → tx kv.TemporalTx
- Non-builder stages that write to
tx directly: add an internal cast rwTx := tx.(kv.TemporalRwTx) — these are only ever called with a real write tx from StageLoop/ProcessFrozenBlocks
MiningStep: remove MemoryBatch, pass kv.TemporalTx (from BeginTemporalRo) directly to Sync.Run
Existing callers of Sync.Run that pass a kv.TemporalRwTx continue to work unchanged since TemporalRwTx satisfies TemporalTx.
Scope
~84 occurrences of kv.TemporalRwTx in stage signatures across execution/. Stages that need write access would cast internally. A good first step would be to identify and enumerate which stages actually write to tx vs which only read, to minimise unsafe casts.
Related
Background
MiningStep(inexecution/stagedsync/stageloop/stageloop.go) builds a new block by running the builder stage pipeline. It has historically opened aBeginTemporalRw(MDBX exclusive write transaction) as the base tx, wrapped it in aMemoryBatchoverlay, and always rolled back the base tx when done. The write transaction was never needed — all actual writes from the mining stages go into theMemoryBatchin-memory buffer, which is discarded without flushing.PR #19821 fixed the immediate symptom — switching
BeginTemporalRwtoBeginTemporalRoto stop holding the MDBX exclusive write lock during block builds. However,MemoryBatchis still present purely as a compatibility shim to satisfy thekv.TemporalRwTxtype required bySync.RunandExecFunc.Why MemoryBatch is now unnecessary
The builder stage pipeline (
builderstages) was refactored so that all state writes go throughSharedDomains, not through the underlyingkv.TemporalRwTx. Inspection of the builder stages confirms:SpawnBuilderCreateBlockStage— only reads fromtx(rawdb.ReadHeaderByNumber,rawdb.ReadHeadersByNumber)SpawnBuilderExecStage— only reads fromtx(rawdb.ReadHeader)StageExecuteBlocksCfg(forward path) — writes state exclusively throughSharedDomainsNo builder stage writes directly to
tx.MemoryBatchexists only to provide akv.TemporalRwTx-shaped object toSync.Run.Proposed fix
Broaden the interfaces in the stage pipeline so the builder path can use a plain read-only tx:
ExecFunc/UnwindFunc(execution/stagedsync/stage.go): changerwTx kv.TemporalRwTx→tx kv.TemporalTxSync.Run,Sync.RunUnwind,Sync.RunNoInterrupt,Sync.RunSnapshots(execution/stagedsync/sync.go): changetx kv.TemporalRwTx→tx kv.TemporalTxtxdirectly: add an internal castrwTx := tx.(kv.TemporalRwTx)— these are only ever called with a real write tx fromStageLoop/ProcessFrozenBlocksMiningStep: removeMemoryBatch, passkv.TemporalTx(fromBeginTemporalRo) directly toSync.RunExisting callers of
Sync.Runthat pass akv.TemporalRwTxcontinue to work unchanged sinceTemporalRwTxsatisfiesTemporalTx.Scope
~84 occurrences of
kv.TemporalRwTxin stage signatures acrossexecution/. Stages that need write access would cast internally. A good first step would be to identify and enumerate which stages actually write totxvs which only read, to minimise unsafe casts.Related