Skip to content

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

@panos-xyz

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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions