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
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-primitives — MorphHeader.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
Acceptance Criteria
References
Background
morph-l2/go-ethereum#332proposes switching L2 block time from second-resolution to millisecond-resolution. To stay byte-for-byte interoperable withmorph-geth,morph-rethmust mirror the same encoding decisions everywhereHeader.timestamp, payload-attributes timestamps, and RPC time-related fields are touched.Open Questions for Upstream Spec
Header.timestampasu64seconds and add a separatetimestamp_ms_offsetfield, OR redefineHeader.timestampto mean milliseconds wholesale, OR packseconds * 1000 + msinto the existingu64?MorphHardfork::Onyx) or piggy-back on an existing planned fork (Jade)?eth_getBlockBy*/eth_blockNumber/eth_getLogskeep returning seconds for backward compat, with millisecond access via a newmorph_*RPC, or break compat at the fork?is_*_active_at_timestampcurrently take seconds; do they switch to ms, or do we keep seconds-domain everywhere except block production?Functional Description
Once the encoding is final, mirror it in
morph-rethso block production, validation, payload assembly, and RPC all agree withmorph-gethto the millisecond.Touch points (reth side)
The following crates / files will need adaptation. Exact changes depend on the chosen encoding:
morph-primitives—MorphHeader.timestampfield type / encoding;MorphBlockround-trip with the new layout; RLP equivalence with go-ethereummorph-chainspecis_morph203_active_at_timestamp,is_viridian_active_at_timestamp,is_emerald_active_at_timestamp,is_jade_active_at_timestamppredicates — pick a single timestamp domain (s or ms) and stick with itOnyx?) gates the switch: register inMorphHardfork, addFrom<SpecId>mapping, populate mainnet / hoodiextra_fieldsmorph-payload-typesExecutableL2Data.timestampandSafeL2Data.timestampJSON encodingMorphPayloadAttributesbuildermorph-payload-builderblock_timemath)morph-evmMorphBlockAssembler::extra_datais unaffected, but verifyBlockEnv.timestampplumbing matches what EVM opcodeTIMESTAMPshould return (geth historically returns seconds — confirm post-fork behavior)morph-engine-apiengine_assembleL2Block/engine_newL2Block/engine_validateL2Block/engine_newSafeL2Blockargument shapesmorph-rpceth_getBlockBy*JSON output — backward-compat decision lives heremorph_*namespace addition for sub-second accessmorph-consensusmorph-engine-tree-extScope
MorphHardfork::<X>+ activation timestamps in mainnet.json / hoodi.jsonAcceptance Criteria
cargo nextest run --workspacepassescargo nextest run -p morph-node --features test-utils -E 'binary(it)'passesReferences