Skip to content

Feat: Sequencer Final PR #966

Open
tomatoishealthy wants to merge 25 commits into
mainfrom
feat/sequencer-final
Open

Feat: Sequencer Final PR #966
tomatoishealthy wants to merge 25 commits into
mainfrom
feat/sequencer-final

Conversation

@tomatoishealthy
Copy link
Copy Markdown
Contributor

No description provided.

allen.wu and others added 24 commits May 28, 2026 17:04
- 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>
@tomatoishealthy tomatoishealthy requested a review from a team as a code owner May 29, 2026 09:51
@tomatoishealthy tomatoishealthy requested review from secmgt and removed request for a team May 29, 2026 09:51
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Warning

Review limit reached

@tomatoishealthy, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 17 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef12822e-adf0-4b10-ab36-86a1c40e3138

📥 Commits

Reviewing files that changed from the base of the PR and between aeb86c6 and e7ba4e0.

⛔ Files ignored due to path filters (10)
  • bindings/go.sum is excluded by !**/*.sum
  • common/go.sum is excluded by !**/*.sum
  • contracts/go.sum is excluded by !**/*.sum
  • go.work.sum is excluded by !**/*.sum
  • node/go.sum is excluded by !**/*.sum
  • ops/l2-genesis/go.sum is excluded by !**/*.sum
  • ops/tools/go.sum is excluded by !**/*.sum
  • oracle/go.sum is excluded by !**/*.sum
  • token-price-oracle/go.sum is excluded by !**/*.sum
  • tx-submitter/go.sum is excluded by !**/*.sum
📒 Files selected for processing (81)
  • Makefile
  • bindings/bindings/l1sequencer.go
  • bindings/go.mod
  • common/batch/batch_cache.go
  • common/go.mod
  • contracts/contracts/l1/L1Sequencer.sol
  • contracts/contracts/test/L1Sequencer.t.sol
  • contracts/contracts/test/base/L1SequencerBase.t.sol
  • contracts/deploy/022-SequencerInit.ts
  • contracts/go.mod
  • go-ethereum
  • node/blocktag/config.go
  • node/blocktag/service.go
  • node/cmd/node/main.go
  • node/core/executor.go
  • node/core/sequencers.go
  • node/core/sync.go
  • node/db/keys.go
  • node/db/store.go
  • node/derivation/batch_decode.go
  • node/derivation/batch_info.go
  • node/derivation/config.go
  • node/derivation/config_test.go
  • node/derivation/database.go
  • node/derivation/derivation.go
  • node/derivation/finalizer.go
  • node/derivation/metrics.go
  • node/derivation/reorg.go
  • node/derivation/static_scan_test.go
  • node/derivation/tag_advance.go
  • node/derivation/tag_advance_test.go
  • node/derivation/verify.go
  • node/derivation/verify_local.go
  • node/flags/flags.go
  • node/go.mod
  • node/hakeeper/block_fsm.go
  • node/hakeeper/block_payload.go
  • node/hakeeper/config.go
  • node/hakeeper/ha.toml.example
  • node/hakeeper/ha_service.go
  • node/hakeeper/leader_monitor.go
  • node/hakeeper/rpc/api.go
  • node/hakeeper/rpc/auth.go
  • node/hakeeper/rpc/auth_test.go
  • node/hakeeper/rpc/backend.go
  • node/hakeeper/rpc/client.go
  • node/hakeeper/rpc/server.go
  • node/hakeeper/rpc/types.go
  • node/l1sequencer/signer.go
  • node/l1sequencer/verifier.go
  • node/ops-morph/docker-compose-validator.yml
  • node/sequencer/tm_node.go
  • node/sync/syncer.go
  • node/sync/syncer_test.go
  • node/types/retryable_client.go
  • node/types/retryable_client_test.go
  • node/validator/config.go
  • node/validator/validator.go
  • node/validator/validator_test.go
  • ops/devnet-morph/devnet/setup_nodes.py
  • ops/docker-sequencer-test/Dockerfile.l2-node-test
  • ops/docker-sequencer-test/Dockerfile.tx-submitter-test
  • ops/docker-sequencer-test/check-whitelist-test.sh
  • ops/docker-sequencer-test/docker-compose.ha-override.yml
  • ops/docker-sequencer-test/docker-compose.override.yml
  • ops/docker-sequencer-test/docker-compose.reorg-test.override.yml
  • ops/docker-sequencer-test/run-ha-test.sh
  • ops/docker-sequencer-test/run-perf-test.sh
  • ops/docker-sequencer-test/run-test.sh
  • ops/docker/.env
  • ops/docker/docker-compose-4nodes.yml
  • ops/docker/docker-compose-reth.yml
  • ops/docker/ha-nodekey0
  • ops/docker/ha-nodekey1
  • ops/docker/ha-nodekey2
  • ops/docker/node5/node_key.json
  • ops/l2-genesis/go.mod
  • ops/tools/go.mod
  • oracle/go.mod
  • token-price-oracle/go.mod
  • tx-submitter/go.mod
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sequencer-final

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant