Skip to content

feat(consensus): mirror block time switch to milliseconds (go-ethereum#332) #123

Description

@panos-xyz

Background

morph-l2/go-ethereum#332 proposes switching L2 block time from second-resolution to millisecond-resolution. To stay byte-for-byte interoperable with morph-geth, morph-reth must mirror the same encoding decisions everywhere Header.timestamp, payload-attributes timestamps, and RPC time-related fields are touched.

⚠️ Upstream spec is not yet finalized (morph-l2/go-ethereum#332, assigned to @SegueII). This issue stages the work so we can move quickly once the spec lands; concrete encoding/activation choices below are placeholders to be tightened against geth's implementation PR.

Open Questions for Upstream Spec

  • Encoding strategy: keep Header.timestamp as u64 seconds and add a separate timestamp_ms_offset field, OR redefine Header.timestamp to mean milliseconds wholesale, OR pack seconds * 1000 + ms into the existing u64?
  • Activation: gate behind a new hardfork (e.g. MorphHardfork::Onyx) or piggy-back on an existing planned fork (Jade)?
  • RPC compatibility: do eth_getBlockBy* / eth_blockNumber / eth_getLogs keep returning seconds for backward compat, with millisecond access via a new morph_* RPC, or break compat at the fork?
  • Hardfork activation predicates: is_*_active_at_timestamp currently take seconds; do they switch to ms, or do we keep seconds-domain everywhere except block production?
  • Genesis timestamp semantics: still seconds, with the ms component starting at fork activation?

Functional Description

Once the encoding is final, mirror it in morph-reth so block production, validation, payload assembly, and RPC all agree with morph-geth to the millisecond.

Touch points (reth side)

The following crates / files will need adaptation. Exact changes depend on the chosen encoding:

  • morph-primitivesMorphHeader.timestamp field type / encoding; MorphBlock round-trip with the new layout; RLP equivalence with go-ethereum
  • morph-chainspec
    • is_morph203_active_at_timestamp, is_viridian_active_at_timestamp, is_emerald_active_at_timestamp, is_jade_active_at_timestamp predicates — pick a single timestamp domain (s or ms) and stick with it
    • genesis timestamp parsing
    • if a new hardfork (Onyx?) gates the switch: register in MorphHardfork, add From<SpecId> mapping, populate mainnet / hoodi extra_fields
  • morph-payload-types
    • ExecutableL2Data.timestamp and SafeL2Data.timestamp JSON encoding
    • MorphPayloadAttributes builder
  • morph-payload-builder
    • block assembly: timestamp slot in header
    • parent timestamp delta enforcement (block_time math)
  • morph-evm
    • MorphBlockAssembler::extra_data is unaffected, but verify BlockEnv.timestamp plumbing matches what EVM opcode TIMESTAMP should return (geth historically returns seconds — confirm post-fork behavior)
  • morph-engine-api
    • engine_assembleL2Block / engine_newL2Block / engine_validateL2Block / engine_newSafeL2Block argument shapes
  • morph-rpc
    • eth_getBlockBy* JSON output — backward-compat decision lives here
    • any morph_* namespace addition for sub-second access
  • morph-consensus
    • header timestamp validation rules (monotonic, parent+1 minimum, future-bounding)
  • morph-engine-tree-ext
    • if a new fork gates the switch and triggers state-root behavior changes, the gate predicate may move there too

Scope

  • Wait for / align with go-ethereum spec PR (linked from #332 once filed)
  • Pick encoding strategy in lock-step with geth
  • If gated by a new hardfork, register MorphHardfork::<X> + activation timestamps in mainnet.json / hoodi.json
  • Implement timestamp encoding everywhere listed under Touch Points
  • Update tests + e2e
  • Add cross-client conformance test (block produced on one client, validated by the other)

Acceptance Criteria

  • morph-reth-built block hashes are byte-identical to morph-geth-built blocks at the same height across the fork boundary
  • cargo nextest run --workspace passes
  • cargo nextest run -p morph-node --features test-utils -E 'binary(it)' passes
  • Existing pre-fork blocks still validate without re-encoding
  • RPC backward-compat decision is documented and tested (whichever way it goes)

References

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions