feat(genesis): emit v2 (257-byte) batch header at bootstrap#970
feat(genesis): emit v2 (257-byte) batch header at bootstrap#970curryxbo wants to merge 29 commits into
Conversation
- L1Sequencer.sol: sequencerHistory[], updateSequencer, getSequencerAt, initializeHistory - Bindings: updated ABI for new contract interface - SequencerVerifier: L1 history cache with interval cursor optimization - Signer: simplified interface (removed IsActiveSequencer) - 022-SequencerInit.ts: fixed initialize call (1 param instead of 2) - Docker: added L1_SEQUENCER_CONTRACT env for all nodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- run-test.sh: added build-malicious and p2p-test commands - docker-compose.override.yml: malicious-geth-0 and malicious-node-0 services - Tests: T-01~T-05 (active attacks) + T-06 (BlockSync pollution) + T-07 (resilience) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix grep -c multiline: use || true instead of || echo "0" - Fix env var loss: malicious override must include full env list - Swap approach: reuse synced sentry instead of fresh malicious container - Uncomment CONSENSUS_SWITCH_HEIGHT for V2 mode activation - Add SEQUENCER_PRIVATE_KEY to node-0 override Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use staking key (0xd998...) as SEQUENCER_PRIVATE_KEY for node-0 - Add initializeHistory() call in setup to register sequencer on L1 - Fixes "no sequencer record" error in V2 mode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- T-06: use blocksync-forge (blocksync/reactor.go) instead of sync-forge (broadcast_reactor.go) - targets the actual V1 vulnerability path - T-06: stop node-3 to create gap, restart to trigger BlockSync - Phase 0: explicit checks for V2 mode, signer, and switch height - T-04: use futureHeight (currentHeight+10000) for deterministic unsolicited test - Separate log files per phase to prevent cross-contamination Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add L1Sequencer.t.sol: 27 Foundry tests covering initialize, initializeHistory, updateSequencer, getSequencerAt binary search edge cases, and access control - Regenerate l1sequencer.go with abigen (bytecode now matches current contract with sequencerHistory[], binary search, etc.) - Update verifier.go: L1SequencerHistoryRecord -> L1SequencerSequencerRecord - Add exponential backoff retry (10s -> 20s -> ... -> 5min) when initial history load fails, instead of waiting full 5 minutes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids stuttering in abigen output (L1SequencerSequencerRecord -> L1SequencerHistoryRecord). No ABI/storage layout change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…om L1 contract Unify the upgrade height source: instead of a CLI flag / env var, the verifier now sets upgrade.UpgradeBlockHeight from the first history record fetched from the L1Sequencer contract. - node/l1sequencer/verifier.go: call SetUpgradeBlockHeight on first successful history load (prev==0) - node/cmd/node/main.go: remove ConsensusSwitchHeight flag read; require L1 Sequencer contract address - node/flags/flags.go: delete ConsensusSwitchHeight flag definition - docker-compose.override.yml: remove 5× MORPH_NODE_CONSENSUS_SWITCH_HEIGHT - run-test.sh: remove set_upgrade_height function, add wait_for_l1_finalized to ensure L1 contract data is finalized before L2 nodes start Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These env var overrides (DEPOSIT_CONTRACT_ADDRESS, SYNC_START_HEIGHT) and the malicious_geth_data volume should be managed via overlay/override files, not by modifying the base compose file directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ncer Add hakeeper module implementing a 3-node Raft cluster for sequencer HA. The HA cluster provides automatic leader election, block replication, and failover without changing the on-chain sequencer identity. node/hakeeper/: - HAService: wraps hashicorp/raft, implements SequencerHA interface - Config: layered loading (defaults -> TOML file -> CLI flags -> resolve -> validate) supports auto-detection of server_id (hostname) and advertised_addr (local IP) - BlockFSM: Raft FSM for block replication; onApplied callback drives geth apply - leaderMonitor: gates block production behind Barrier to ensure log catch-up - rpc/: JSON-RPC admin API (ha_leader, ha_clusterMembership, ha_addServerAsVoter, ha_removeServer, ha_transferLeader, ha_transferLeaderToServer) with HTTP middleware token auth on write operations node/flags/flags.go: - New flags: --ha.enabled, --ha.config, --ha.bootstrap, --ha.join, --ha.server-id, --ha.advertised-addr, --ha.rpc-token node/cmd/node/main.go: - initHAService(): init HA from flags/config when --ha.enabled is set - Fix typed-nil interface bug: pass untyped nil when HA is disabled node/sequencer/tm_node.go: - Pass HA service to tendermint node setup node/go.mod: - Add hashicorp/raft v1.7.1, raft-boltdb/v2 ops/docker-sequencer-test/: - docker-compose.ha-override.yml: 3-node Raft cluster config for devnet - run-ha-test.sh: 29-case integration test suite (config, cluster, block production, failover, admin API, lifecycle) - run-perf-test.sh: performance test harness Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up the new engine_newL2BlockV2 API for reorg support: - Executor.ApplyBlockV2 now returns (applied bool, err error) matching the updated L2Node interface; detects idempotent skips and reorgs using BlockNumber + BlockByNumber checks before calling NewL2BlockV2 - RetryableClient.NewL2BlockV2 wraps the new authclient method with exponential backoff retry; excludes WrongBlockNumberError and ParentNotFoundError from retry (permanent errors) - RetryableClient.AssembleL2BlockV2 added for parent-hash-based block assembly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add BlockHashMismatchError and InvalidNextL1MsgIndexError to the retryableError() exclusion list so the executor stops re-sending invalid payloads back to geth. Made-with: Cursor
… not in PBFT validator set - Add Syncer()/SetSyncer() accessors to Executor for explicit syncer wiring - Start L1 syncer eagerly in main.go for separated-deployment / HA sequencers that are not PBFT validators (lazy-init path never fires for them) - Guard Syncer.Start() with atomic flag to prevent duplicate goroutines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Increase blockCh buffer from 200 to 1000 to reduce drops under load. - Panic on nil onApplied callback in BlockFSM.Apply: this can only happen due to a programmer error (forgot to wire SetOnBlockApplied) and would otherwise silently succeed on the leader while followers diverge. - gofmt: realign one-line method bodies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- go-ethereum: v0.0.0-20260508105911-56deb7072ae4 - tendermint: v0.0.0-20260508065906-9e56b04da3c8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add explicit replace directives in every go.mod to override MVS, because token-price-oracle indirectly required v1.10.14-..., which caused all workspace modules to resolve to the older version and miss new APIs (NewL2BlockV2, AssembleL2BlockV2, SetBlockTags, MorphTxType, updated AssembleL2Block/NewL2Block signatures). Drop the local-path replace block from go.work so the pinned pseudo-version is actually used. Follow-up: investigate the indirect dep that requires v1.10.14 and bump it so these per-module replaces can be removed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull in the persistent-peer ban exemption + sigStore.Close fixes from morph-l2/tendermint feat/sequencer-optimize (commit c6f7e21e4). Updated via 'make update' after bumping TENDERMINT_TARGET_VERSION in the Makefile. All sub-modules tidied. morphnode + tendermint binaries build cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements SPEC-005 derivation verification: - verify_local.go: Path B local-rebuild blob verification (rebuild blockContext + L2 tx blob from local chain, compare against on-chain Rollup batch) - verify.go: extract verifyBatchRoots, gate stateException on real divergence verdicts (not transient errors) - finalizer.go + reorg.go: derivation-driven finalizer + L1 reorg detection (SPEC-005 §4.7.6), rewind-and-reset for canonicality - tag_advance.go + metrics.go: derivation-driven L2 tag management, structured failure diagnostics for Path B Common: export common/batch.BuildBlockContext for derivation reuse. go-ethereum: bump submodule to 045be0fdc7ca (NewL2BlockV2 self-heal). Ops: add second sentry node for derivation validation (4-nodes compose, node5 key, devnet setup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced by derivation-driven tag management (see prior commit): - node/validator/*: removed dedicated validator role - node/blocktag/*: removed standalone block-tag advancer service - node/cmd/node/main.go: drop validator/blocktag wiring - node/flags/flags.go: drop validator-specific CLI flags - ops-morph/docker-compose-validator.yml: drop validator compose file Tags are now advanced inline by the derivation loop, eliminating the extra service and the role-based branching in main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- sync/syncer.go: make Syncer.Start idempotent via sync.Once so re-entry on retry no longer leaks goroutines or races on state - types/retryable_client.go (+test): treat ethereum.NotFound as a permanent failure rather than retrying forever - db/keys.go + db/store.go: derivation-related key helpers used by the new finalizer / tag-advance paths Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Shared L1 client: main.go does a single ethclient.Dial and threads
it into syncer, derivation, l1sequencer Tracker/Verifier/Signer, and
the rollup binding. Reads l1.rpc directly from CLI flags instead of
going through derivation.Config first.
* Derivation reorg: blob-hash mismatch now invokes the tendermint
Node.StopReactorsBeforeReorg → fetch full batch → deriveForce →
StartReactorsAfterReorg(post-reorg height) flow. HA-mode adds a
hard-stop guard (cluster invariant violation; logs full context and
returns instead of self-healing). Mock mode (d.node==nil) skips the
reactor cycle.
* deriveForce uses the new NewL2BlockV2 (*Header, error) return; the
redundant HeaderByNumber readback is gone, parent chains via the
returned header. lastHeader is initialised from the batch's parent
so it tracks the chain head end-to-end.
* Executor.ApplyBlockV2 + RetryableClient.NewL2BlockV2 updated for the
new signature. Executor.updateSequencerSet no longer stops the
syncer when this node ceases to be sequencer — derivation needs it
running on every node.
* deps: bump tendermint to 6393e1eaad71 (derivation reorg API,
StopReactorsBeforeReorg / StartReactorsAfterReorg) and go-ethereum
to 5c5b433f18f2 (NewL2BlockV2 returns header, NextL1MsgIndex
backfill on isSafe path so writeBlockStateWithoutHead's gate passes
when callers don't know the per-block index). Replace directives
for both grouped at the top of every go.mod for review locality.
* docker-sequencer-test: Dockerfile copies common/go.mod alongside
the others (workspace replace requires it). HA override adds
L1_ETH_BEACON_RPC env for ha-node-{0,1,2} (derivation refactor
validates it at startup). run-ha-test.sh service names corrected
from morph-geth-* / sentry-geth-0 to morph-el-* / sentry-el-0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the L1-derived block insertion path off of NewL2BlockV2(isSafe=true) onto NewSafeL2Block, which now accepts SafeL2Data.ParentHash for non-head parents and lets SetCanonical reorg the chain automatically. NewL2BlockV2 becomes sequencer-signed-only (caller supplies pre-computed execution results, gate validates). The previous isSafe=true path on NewL2BlockV2 wrote blocks with caller- supplied StateRoot — for derivation.deriveForce that was the zero hash, because L1 batch metadata only carries the batch-final PostStateRoot, not per-block roots. The resulting blocks had header.Root=0 even though their state was correctly executed and committed, breaking verifyBatchRoots forever and blocking derivation cursor advance. NewSafeL2Block executes internally and fills header.Root from stateDB.IntermediateRoot, so the header on disk is consistent with the state. * node/derivation/derivation.go: deriveForce builds SafeL2Data with ParentHash = lastHeader.Hash() and calls NewSafeL2Block instead of NewL2BlockV2(isSafe=true); safeL2DataToExecutable helper deleted. * node/types/retryable_client.go + node/core/executor.go: drop isSafe arg from NewL2BlockV2. * node/derivation/verify_local.go: outline path got a v0-parent compat shim — only reachable on test/devnet where genesis batch is v0 and V1 is day-1 enabled, so the only v0 batch in the chain is genesis; on prod (V1 day-1, V2 layered on V1) the branch is dead. Reorg semantics only exist post-V2 anyway, so processing pre-V2 via outline is not a load-bearing path. Comment explains the assumption. * deps: bump tendermint to b1b3a3a1d806 (drop dead reorg-restart test harness from Node) and go-ethereum to eb5fbf8f9748 (NewSafeL2Block ParentHash, drop NewL2BlockV2 isSafe). * ops/docker-sequencer-test/Dockerfile.tx-submitter-test: new polyrepo- context Dockerfile so tx-submitter builds against local ../tendermint and ../go-ethereum siblings — the original ops/docker/Dockerfile.submitter only sees the morph repo and can't resolve workspace replace directives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: corey <corey.zhang@bitget.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plumb the morph node into the Nitro Enclave signer (separate
morph-enclave-signer repo) so the sequencer can sign blocks without
ever holding the plaintext key. Two flags are mutually exclusive:
--sequencer.privateKey existing in-process LocalSigner
--sequencer.enclaveSignerAddr new EnclaveSigner over vsock (CID:port)
EnclaveSigner highlights:
- vsock-only, mdlayher/vsock dep added
- one persistent connection reused across Sign calls; dial wrapped
in a goroutine race with dialTimeout (mdlayher/vsock has no
context-aware Dial)
- probe + signOnce both bounded by SetDeadline(requestTimeout) so a
half-open or unresponsive enclave can't stall startup or hold
Sign()'s mutex on reconnect
- up to 3 retries per Sign call with reconnect on each failure,
Error log on exhaustion; caller (tendermint commitBlock) is
expected to treat that as fatal
- identity self-test at construction: sign a 32-byte zero hash,
recover the EVM address from the signature via secp256k1 ECDSA
recovery, abort node startup if it disagrees with the address
GetPubkey reported. Catches misconfig like wrong SECRET_ID baked
into the .eif or vsock-proxy MITM swap before tendermint comes up.
Co-Authored-By: Claude Opus 4.7 (1M context) <<EMAIL_ADDRESS>>
Unify the address format with ops-cli, which already takes
`vsock:CID:port`. parseAddr now accepts both that form and the
legacy bare `CID:port` so existing systemd / k8s configs keep
working through the rollout.
- enclave_signer.go: TrimPrefix("vsock:") in parseAddr
- flags.go: Usage doc reflects both forms
- enclave_signer_test.go: cases for both forms + new error cases
(vsock:16, vsock:abc:5000)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GenesisBatchHeader produced a v0 (249-byte) header, while every
subsequent commitBatch on a fresh chain went through v1/v2 (257-byte)
codec. Tooling and audit had to special-case batchIndex 0 as a v0
outlier; the storage layout was inconsistent without payoff.
Bump genesis to v2:
- genesisBatchVersion 0 → 2
- genesisBatchHeaderLength 249 → 257
- write lastBlockNumber (=genesisHeader.Number, 0 for fresh chains)
at offset 249
Rollup.sol's importGenesisBatch is already version-agnostic at the
loader (`_loadBatchHeader` dispatches v1/v2 to BatchHeaderCodecV1
which validates the 257-byte length); field reads use V0 codec
offsets that match across v0/v1/v2 for the leading 249 bytes; the
ZERO_VERSIONED_HASH check on blobVersionedHash holds for v2's
"no blob attached" sentinel. No Solidity changes required.
Mainnet immutability note: chains already imported with a v0 genesis
keep their v0 committedBatches[0] forever — this commit only changes
genesis emission for fresh chains (devnet bootstrap, future mainnets).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
6a7e411 to
6948ba6
Compare
65e4e26 to
9af2c07
Compare
|
Already merged into |
Summary
GenesisBatchHeadernow emits a v2 (257-byte) header instead of v0 (249-byte).lastBlockNumberfield at offset 249 (= 0 for fresh genesis).Rollup.importGenesisBatchalready accepts v1/v2 layouts via_loadBatchHeaderdispatch; v0/v1/v2 share the leading 249-byte layout so the V0-codec field reads inimportGenesisBatchwork uniformly; theZERO_VERSIONED_HASHcheck onblobVersionedHashholds for v2's "no blob attached" sentinel.Why
Every subsequent batch on a fresh chain uses the 257-byte v1/v2 layout — only
batchIndex 0was a v0 outlier. Tooling, audits, and downstream parsers had to special-case the genesis row without any payoff. Bumping genesis to v2 makes batch storage layout uniform across the chain.Mainnet immutability
This change only affects genesis emission for fresh chains (devnet bootstrap, future mainnet redeploys). Chains already deployed with a v0 genesis keep their v0
committedBatches[0]permanently — no migration involved.Test plan
go test ./ops/l2-genesis/morph-chain-ops/genesis/...— PASS (existinglayer_two_test.go::TestBuildL2DeveloperGenesisexercisesGenesisBatchHeader)len=257,version=2,blobVersionedHash=0x010657...4014(ZERO marker),postStateRootpopulated,lastBlockNumber=0x0000000000000000Rollup.importGenesisBatch(headerBytes)succeeds and downstreamcommitBatchflows continue normally.common/batchparsing (uses an independent v0 fixture for cache init test, unaffected).🤖 Generated with Claude Code