What happened
A pass over morph-geth miner, genesis morph config, and Engine API vs morph-reth shows several sequencer/builder knobs that geth honors and morph-reth either ignores, hard-codes, or only applies on the packing path.
This is the tracking issue. Child issues cover independently fixable slices.
What I expected
A morph-reth sequencer should produce blocks that a morph-geth validator would accept, and operators should have the same runtime levers they have on geth (miner.gaslimit, genesis tx/payload caps, L1-message overflow behavior).
Audit (geth vs morph-reth)
Must-fix (protocol / sequencer liveness)
| Item |
morph-geth |
morph-reth today |
Block header gasLimit target |
--miner.gaslimit / miner_setGasLimit; each block ramps toward GasCeil by ~1/1024 |
Sequencer assemble always copies parent. Header is frozen at genesis (30M). --builder.gaslimit / --miner.gaslimit parse but are unused. |
Genesis morph.maxTxPerBlock |
Mainnet genesis is 100; miner stops packing at that count |
Field is dropped when reading genesis. Only --morph.max-tx-per-block exists and defaults to unlimited. |
Genesis morph.maxTxPayloadBytesPerBlock |
122880; ValidateBody rejects oversized L2 payload |
Parsed, and used as a builder packing cap via CLI default 122880. Not checked on import. A morph-reth validator would accept a block a geth validator would reject. |
| L1 message that does not fit remaining gas |
Seal the block with what already fits; leftover L1 messages go to the next block |
assembleL2Block fails the whole build. Sequencer can stall. |
Should-fix (operator knobs / defaults)
| Item |
morph-geth |
morph-reth today |
| Block-build time budget |
--miner.newblock-timeout; unset sanitizes to 3s |
Packing breaker is hard-coded 1s. Comment says use --builder.deadline, but that flag is not wired into the Morph packing breaker (reth default is 12s for a different job). |
| Miner gas-price floor when packing |
--miner.gasprice / miner_setGasPrice, default 0.001 gwei |
No equivalent packing floor. Txpool minimum_priority_fee is optional and separate. |
miner.maxaccountsnum |
Caps how many accounts the miner pulls from the pool |
Missing. |
miner RPC (setGasLimit, setGasPrice, setExtra) |
Present (non-public namespace) |
Missing. setExtra is a no-op on Morph anyway because extra-data must be empty. |
Not a gap (checked)
miner.extradata: Morph consensus zeros extra-data; both clients require empty extra.
- Engine API surface (
assembleL2Block(V2), newL2Block(V2), validateL2Block, newSafeL2Block, setBlockTags): present.
- Fee vault, base-fee cap (10 gwei), 1/1024 gasLimit validation of imported headers: present.
--morph.max-tx-payload-bytes packing cap default matches genesis 122880.
eth_maxPriorityFeePerGas default was already aligned to geth's 0.001 gwei.
Steps to reproduce
- Compare a morph-geth sequencer started with defaults against morph-reth on the same chain (mainnet genesis
gasLimit 30M, maxTxPerBlock 100, maxTxPayloadBytesPerBlock 122880).
- Try to raise block gasLimit at runtime (
miner.setGasLimit) — geth ramps, morph-reth cannot.
- Assemble a block without
--morph.max-tx-per-block 100 — morph-reth may pack more than 100 txs.
- Import a block whose L2 payload exceeds 122880 bytes — geth rejects, morph-reth accepts.
Additional context
This is not an OP-stack-style SystemConfig.gasLimit. Morph's consensus node copies gasLimit from assembleL2Block; the EL miner owns the target, same as geth. OP/Base put the value on op-node / L1 SystemConfig; Tempo/Scroll wire --builder.gaslimit on the EL. morph-reth currently does neither on the sequencer assemble path.
Child issues:
- header gasLimit target (this conversation)
- genesis
maxTxPerBlock ignored
- payload size not consensus-validated
- L1 message gas overflow aborts assemble
What happened
A pass over morph-geth miner, genesis
morphconfig, and Engine API vs morph-reth shows several sequencer/builder knobs that geth honors and morph-reth either ignores, hard-codes, or only applies on the packing path.This is the tracking issue. Child issues cover independently fixable slices.
What I expected
A morph-reth sequencer should produce blocks that a morph-geth validator would accept, and operators should have the same runtime levers they have on geth (
miner.gaslimit, genesis tx/payload caps, L1-message overflow behavior).Audit (geth vs morph-reth)
Must-fix (protocol / sequencer liveness)
gasLimittarget--miner.gaslimit/miner_setGasLimit; each block ramps towardGasCeilby ~1/1024--builder.gaslimit/--miner.gaslimitparse but are unused.morph.maxTxPerBlock--morph.max-tx-per-blockexists and defaults to unlimited.morph.maxTxPayloadBytesPerBlockassembleL2Blockfails the whole build. Sequencer can stall.Should-fix (operator knobs / defaults)
--miner.newblock-timeout; unset sanitizes to 3s--builder.deadline, but that flag is not wired into the Morph packing breaker (reth default is 12s for a different job).--miner.gasprice/miner_setGasPrice, default 0.001 gweiminimum_priority_feeis optional and separate.miner.maxaccountsnumminerRPC (setGasLimit,setGasPrice,setExtra)setExtrais a no-op on Morph anyway because extra-data must be empty.Not a gap (checked)
miner.extradata: Morph consensus zeros extra-data; both clients require empty extra.assembleL2Block(V2),newL2Block(V2),validateL2Block,newSafeL2Block,setBlockTags): present.--morph.max-tx-payload-bytespacking cap default matches genesis 122880.eth_maxPriorityFeePerGasdefault was already aligned to geth's 0.001 gwei.Steps to reproduce
gasLimit30M,maxTxPerBlock100,maxTxPayloadBytesPerBlock122880).miner.setGasLimit) — geth ramps, morph-reth cannot.--morph.max-tx-per-block 100— morph-reth may pack more than 100 txs.Additional context
This is not an OP-stack-style SystemConfig.gasLimit. Morph's consensus node copies
gasLimitfromassembleL2Block; the EL miner owns the target, same as geth. OP/Base put the value onop-node/ L1 SystemConfig; Tempo/Scroll wire--builder.gaslimiton the EL. morph-reth currently does neither on the sequencer assemble path.Child issues:
maxTxPerBlockignored