fix(anvil): continue the saved timeline after loading state - #15760
Conversation
Backend::load_state restores the block env, base fee and blob params from the saved head but never re-anchors the TimeManager, so the first block mined after loading a state file took its timestamp from the node's startup anchor (genesis or fork block) instead of the saved chain head, jumping back in time. Reset block time to the loaded head's timestamp where the fees are already restored from that header, like the rollback path does, skipping the case where the canonical head is a newer fork block.
mattsse
left a comment
There was a problem hiding this comment.
The equal-height fork case can still replace the canonical fork time anchor with the loaded state's timestamp. Please use canonical head identity for this guard and add coverage for a loaded state and fork head at the same height.
Compare the loaded head's hash with the canonical best hash instead of comparing block numbers, so an equal-height fork head keeps its time anchor. Covered by a test that forks a node at the same height as the loaded state's head.
0xMars42
left a comment
There was a problem hiding this comment.
Good catch, the equal-height case did fire the reset: reproduced with a test forking a node at the same height as the loaded state's head, which failed on the previous guard and passes now. The guard compares the loaded head's hash with the canonical best hash instead of the numbers.
mattsse
left a comment
There was a problem hiding this comment.
The follow-up now guards the time re-anchor by canonical head identity and adds the requested equal-height fork regression. The focused regression test passes locally.
|
This pull request is stale because it has been open for 14 days with no activity. It will be closed in 7 days if there is no further activity. |
✅ Changelog foundThe deterministic check will validate the changed entry. |
Head branch was pushed to by a user without write access
# Conflicts: # crates/anvil/src/eth/backend/mem/mod.rs
Hold the mining lock while applying loaded state so block production cannot race the chain-head and timestamp updates. Re-anchor fork time by canonical head hash so blockless legacy states also clear pending timestamp controls.
Motivation
Fixes #10331.
Backend::load_staterestores the block env, base fee and blob params from the saved head, but never re-anchors theTimeManager. The first block mined after loading a state file therefore takes its timestamp from the node's startup anchor (genesis or fork block) instead of the saved chain head: the loaded chain sits on its saved timeline while the next block jumps back to wall-clock time, which can produce a non-monotonic chain.Solution
Re-anchor block time to the canonical head
load_stateselects. When the loaded head stays canonical (plain load, or a state file ahead of the fork block, see #9539), the anchor is its header timestamp read from storage. When the state file is at or below the fork block (see #9215), the canonical head rolls back to the fork block, whose header is not in local storage, so the anchor takes the fork timestamp, the same sourcereset_forkuses. Resolving the head by identity also keeps the timeline of stale blocks out of the anchor: a state file can carry blocks above its own best block (load_blocksnever prunes, so dumping after loading an older state keeps the discarded blocks), and the highest loaded header is then not the canonical head.This also applies to the
anvil_loadStateRPC, which goes through the same path: loading a state at runtime re-anchors block time the same way, consistent with--load-stateat startup.Two deliberate semantic notes: a pending
evm_setNextBlockTimestampand a previously appliedevm_increaseTimeoffset do not survive a load, matching the other re-anchor paths (anvil_reset, snapshot revert); and--timestampcombined with--load-statenow resolves to the loaded head's timeline (previously the flag survived the load, which could produce a non-monotonic chain against the loaded head).Regression tests: the original one (chain moved a year ahead of wall-clock, saved, reloaded on a fresh node: the next block continues the saved timeline; fails on master with
block after load_state went back in time: 1784120095 < 1815656095), the equal-height fork case (fork anchor kept), the fork rollback (future-dated local block, load a height-0 state onto the fork:latestreturns the exact fork head and the next block leaves the discarded timeline), and a dump carrying stale blocks above its best block (the next block continues the canonical timeline). The last two fail on the previous revision of this branch.PR Checklist