fix(event): keep event notifications consistent with committed blocks
What does this PR do?
- Posts contract triggers only after the block that produced them is committed: from
blockTriggeron the regular path, and after the whole candidate branch has been applied on a fork switch. They stay ahead of the block trigger, so the order seen by the event plugin is unchanged. - After a failed fork switch restores the previous branch, re-emits that branch's forward notifications: contract triggers, JSON-RPC logs filter events, and event-subscribe block and transaction triggers. The JSON-RPC block filter is left alone because its original notification was never withdrawn. If the restore itself fails partway, the blocks already recommitted are re-emitted before the failure propagates.
- Treats a notification failure after a fork switch the way
blockTriggeralready does: the ledger is committed and the notification cannot be repaired, so the node requests exit. On the restore path the original switch exception is attached as suppressed.
Why are these changes required?
Contract triggers were posted at the end of processTransaction, as each transaction executed. Anything that throws later inside applyBlock lands in the catch (Throwable) of pushBlock, which rolls back the session, drops the block from khaosDb and rethrows. Nothing there withdraws the triggers already posted for the block's earlier transactions. A fork switch has the same gap: when a later block of the candidate branch fails, switchFork erases the applied blocks but their triggers stay published. Subscribers were left holding forward events (removed=false) for blocks that never became canonical, with no removal to follow.
The restore path had the mirror problem, missing events instead of extra ones. Erasing the previous branch withdrew its logs filter events and block/transaction triggers (removed=true). Re-applying it went through applyBlock, which re-posted contract triggers from processTransaction, but reApplyBlockEvents runs only when the candidate branch succeeds, so the restored branch's logs filter events and block/transaction triggers were never published again.
This PR has been tested by:
- Unit Tests:
ManagerTestpushBlockShouldPostContractTriggersOnlyAfterCommit: through the real VM, a block that fails after executing a contract deployment posts nothing; the committed block posts one forward trigger. Fails on the previous code.switchForkShouldReemitOldBranchEventsAfterFailedCandidateSwitch: the old head's logs filter and contract trigger go forward, removed, restored; its block filter is not republished; the failed candidate blocks emit nothing;eth_getFilterChangesreturns the removal, then the restoration.switchForkShouldReemitRestoredPrefixEventsWhenRestoreFails: through the real VM, a two-block old branch whose restore fails on the second block; the first block's contract and logs filter events are withdrawn then republished, its block filter is not, and the failed blocks emit nothing. Fails on the previous code.testPostContractTriggersUsesAppliedBlockTraceAndCapturedSolidifiedHeight,testPostForkBranchEventsRequestsExitOnReplayFailure,postContractTriggersSkipsTransactionsWithoutTrace: the deferred-trigger helper on its own (block hash and solidified height, exit on replay failure, skipping a transaction without a trace).
Follow up
None.
Extra details
Deferred triggers are rebuilt from the transaction traces kept on the applied block, so nothing is copied while the outcome is unknown. The solidified block number attached to them is captured before the block is applied, which is the value the immediate path reported too: processTransaction runs before the consensus step that advances it. The forward and reorg-removal paths share one per-block helper, so the removal path now also skips a transaction loaded without a trace (a block read from the database after a restart) instead of dereferencing null. processTransaction no longer reads the block's merkle root, so ManagerMockTest moves its timing stub to another call to keep covering the slow-transaction log branch.
Summary by cubic
Fixes event notifications so subscribers only see events for blocks that actually become canonical: contract triggers are deferred until commit, and a failed fork switch now republishes the restored branch's events.
- Contract triggers were posted during transaction execution; a block that failed afterward, or a candidate branch that failed to switch, left forward notifications with no matching removal. They now post from
blockTriggeron the regular path and after the whole branch is applied on a fork switch. - After a failed fork switch restores the previous branch, that branch's contract triggers, logs filter events, and event-subscribe block/transaction triggers are re-emitted. The JSON-RPC block filter is not, since its original notification was never withdrawn.
- If the restore fails partway, the already re-committed prefix is re-emitted before the failure propagates.
- A notification failure after a fork switch now requests node exit, matching how
blockTriggeralready handles it. - The shared per-block helper also makes the reorg-removal path skip transactions loaded without a trace after a restart instead of dereferencing null.
- Adds tests covering deferred triggers through the real VM, failed-switch restoration, partial-restore failure, and the null-trace skip.