Skip to content

anvil mines consecutive blocks with identical timestamps on default automine path #16544

Description

@gomesalexandre

This is the repro @grandizzy asked for in #9142:

@Rubilmax mind to open a ticket with a way to reproduce the problem you see? thank you!

That ticket never got filed, so here it is.

What it says on the box

Default anvil (default automine, no --block-time, no setBlockTimestampInterval tweak) mines consecutive blocks with the identical block.timestamp. Confirmed with four ordinary cast send transfers, no flags:

block 2 ts 1788310641
block 3 ts 1788310641
block 4 ts 1788310641

A forward time jump makes it worse - after anvil_mine(1, 10000), every subsequent block freezes at that same timestamp:

blocks 2-5 all ts 1788320438

Where

crates/anvil/src/eth/backend/time.rs:181-183:

// Ensures that the timestamp is always increasing
if next_timestamp < last_timestamp { next_timestamp = last_timestamp + 1; }

Comment says "always increasing", comparison is <, so equality sails through untouched. Sibling set_next_block_timestamp (~line 127/131) has a doc comment saying it "fails if it's before (or at the same time) the last timestamp" but only checks < too.

Why it matters

Yellow Paper eq. 55 requires Hs > P(H)Hs (strictly increasing). geth rejects header.Time <= parent.Time with errOlderBlockTime. Hardhat and Ganache both guarantee parent+1. anvil is the odd one out on the default path.

Concretely, this makes downstream test suites go green when they should catch a real bug:

  • Uniswap V2 _update: if (timeElapsed > 0) - TWAP accumulators never accumulate across same-second blocks
  • Uniswap V3 Oracle.write: if (last.blockTimestamp == blockTimestamp) return - the observation write is silently dropped
  • Synthetix StakingRewards.rewardPerToken and similar reward-streaming contracts: zero accrual for that block

A TWAP-manipulation guard test written against anvil can pass vacuously because the timestamp never actually advances between the manipulation block and the read block.

Prior art

#13694 proposed exactly this fix (< -> <=) and got a positive review (zerosnacks: "Makes sense, thanks!"), then the stale-bot closed it 2026-06-29 for inactivity, not on merit.

The catch: #9142 itself is a legitimate feature request for same-timestamp blocks under --block-time 0, and it shipped. A naive blanket <= would break that. The distinction that needs preserving: same-timestamp is fine when it was explicitly requested (interval == Some(0) or an exact setBlockTimestampInterval/setNextBlockTimestamp override), but should not leak into the default automine path where nothing asked for it. That's plausibly why #13694 stalled rather than merged outright - it needed that narrower framing, not just the comparison flip.

Suggested fix shape

Clamp to strictly-increasing on the default path; only permit equal timestamps when the same-timestamp mode was explicitly opted into (matching what #9142 actually asked for, not a blanket relaxation).

Happy to open a PR with that narrower fix if that's the right shape - flagging as an issue first since #13694's exact form apparently needed adjustment.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

  • Status
    Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions