Skip to content

fix: use parent timestamps for activation boundaries#3697

Draft
0x00101010 wants to merge 5 commits into
200msfrom
fix/parent-timestamp-semantics
Draft

fix: use parent timestamps for activation boundaries#3697
0x00101010 wants to merge 5 commits into
200msfrom
fix/parent-timestamp-semantics

Conversation

@0x00101010

Copy link
Copy Markdown
Contributor

Uses actual parent timestamps for first-block upgrade checks in the sequencing and protocol paths so activation boundaries no longer rely on reconstructing a parent with child_timestamp - block_time. It also renames the timestamp-to-block helper to make its lower-bound semantics explicit when multiple blocks can share the same second.

self.rollup.log_upgrade_activation(
envelope.execution_payload.block_number(),
envelope.execution_payload.timestamp(),
None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The follower path passes None for parent_timestamp, falling back to the estimated is_first_X_block which is the heuristic this PR is trying to move away from. The EngineProcessor has access to the current unsafe head via self.engine.state().sync_state — its timestamp is the parent of the payload being inserted. Consider passing Some(unsafe_head_timestamp) here so the follower path also benefits from the accurate parent-aware check.

This is the same pattern used in the sequencer build path (build.rs:202-205), where Some(unsafe_head.block_info.timestamp) is correctly passed.

Comment on lines +160 to +161
if cfg.is_first_jovian_block(self.timestamp, self.timestamp.saturating_sub(cfg.block_time))
&& !self.transactions.is_empty()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_batch receives l2_safe_head whose block_info.timestamp is the actual parent timestamp, yet this line still reconstructs the parent via self.timestamp.saturating_sub(cfg.block_time) — the exact heuristic this PR is eliminating elsewhere.

Consider using the real parent timestamp:

Suggested change
if cfg.is_first_jovian_block(self.timestamp, self.timestamp.saturating_sub(cfg.block_time))
&& !self.transactions.is_empty()
if cfg.is_first_jovian_block(self.timestamp, l2_safe_head.block_info.timestamp)

@github-actions

Copy link
Copy Markdown
Contributor

🔄 base-std fork tests: running...

Dependency Ref Commit
base-std main 4658f1b7
base-anvil 0092692587d8d064dd2c6923ce26a682c58f3694 00926925

View run

@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

The core change — adding an explicit parent_timestamp parameter to is_first_*_block() methods so activation boundaries use the actual parent timestamp instead of reconstructing it via timestamp - block_time — is correct and well-motivated. With sub-second block times, multiple blocks can share the same second-granularity timestamp, making the old heuristic unreliable.

Findings (from prior inline comments, confirmed on re-review)

1. crates/consensus/protocol/src/batch/single.rs:160 — heuristic still used despite real parent being available

check_batch receives l2_safe_head (whose .block_info.timestamp is the actual parent timestamp), yet the Jovian transition check reconstructs the parent via self.timestamp.saturating_sub(cfg.block_time) — the exact heuristic this PR is eliminating elsewhere. Should use l2_safe_head.block_info.timestamp instead.

2. crates/consensus/service/src/actors/engine/engine_request_processor.rs:462 — follower path falls back to estimation

The follower path calls log_upgrade_activation_estimated, which internally falls back to the timestamp - block_time heuristic. The EngineProcessor has access to the unsafe head via engine state, and could pass the real parent timestamp — matching the pattern already used in the sequencer build path (build.rs:202-205).

What looks good

  • The macro-based rollup_fork_methods! change is clean — single source of truth for all fork boundary checks.
  • log_upgrade_activation / log_upgrade_activation_estimated split clearly documents when estimation is being used vs. when the real parent is known.
  • first_logged_upgrade_activation + emit_upgrade_activation refactor reduces repetition in the upgrade logging path.
  • block_number_lower_bound_from_timestamp rename accurately reflects floor-division semantics.
  • Tests cover the same-second boundary scenario (parent_timestamp == timestamp), which is the key edge case this PR addresses.
  • New test test_try_new_ecotone_same_second_boundary_uses_parent_timestamp validates that when parent and child share a timestamp at the ecotone boundary, the Ecotone (not Bedrock) encoding is correctly selected.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant