From d62afab20deb1d30abb8f962b6cd55d5487d6d99 Mon Sep 17 00:00:00 2001 From: "allen.wu" Date: Tue, 4 Aug 2026 15:12:19 +0800 Subject: [PATCH] feat(node): add TEST-ONLY start-in-sequencer-mode devnet switch Add an isolated, test-only devtool package that lets a node boot directly in sequencer mode (skipping the pre-upgrade PBFT phase) for HA testnet / devnet bring-up. Enabled via --startInSequencerMode / MORPH_NODE_START_IN_SEQUENCER_MODE; default off, so production and normal devnet runs are unaffected. - node/devtool/start_in_sequencer_mode.go: flag + ApplyStartInSequencerMode; self-registers via init() so node/flags/flags.go is untouched. Pre-sets the upgrade block height to 0 so IsUpgraded(1) is true and the node starts the sequencer routines directly, never entering the PBFT consensus reactor. - node/cmd/node/main.go: one guarded call before the upgrade store is wired. - docker-compose-cluster.yml / docker-compose-devnet.yml: default the HA cluster to start-in-sequencer-mode and run node-0 as a local-verify P2P follower. TEST/TESTNET-ONLY. Never enable on a production network. Co-Authored-By: Claude Opus 4.8 (1M context) --- node/cmd/node/main.go | 7 ++++ node/devtool/start_in_sequencer_mode.go | 56 +++++++++++++++++++++++++ ops/docker/docker-compose-cluster.yml | 12 ++++++ ops/docker/docker-compose-devnet.yml | 6 +++ 4 files changed, 81 insertions(+) create mode 100644 node/devtool/start_in_sequencer_mode.go diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index dfe032ec3..f905f95a3 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -29,6 +29,7 @@ import ( node "morph-l2/node/core" "morph-l2/node/db" "morph-l2/node/derivation" + "morph-l2/node/devtool" "morph-l2/node/flags" "morph-l2/node/hakeeper" "morph-l2/node/l1sequencer" @@ -91,6 +92,12 @@ func L2NodeMain(ctx *cli.Context) error { "network", sequencerUpgradeNetwork(ctx), "upgradeTimeMs", upgrade.UpgradeBlockTime()) + // TEST-ONLY (devnet/testnet): optionally start directly in sequencer mode + // (pre-set the upgrade block height, skip the PBFT phase). No-op unless + // --startInSequencerMode / MORPH_NODE_START_IN_SEQUENCER_MODE is set. Must run + // before the upgrade store is wired (SetupNode below). See node/devtool. + devtool.ApplyStartInSequencerMode(ctx, nodeConfig.Logger) + home, err := homeDir(ctx) if err != nil { return err diff --git a/node/devtool/start_in_sequencer_mode.go b/node/devtool/start_in_sequencer_mode.go new file mode 100644 index 000000000..adaea5ae1 --- /dev/null +++ b/node/devtool/start_in_sequencer_mode.go @@ -0,0 +1,56 @@ +// Package devtool holds TEST / TESTNET-ONLY developer helpers for devnet and HA +// testnet bring-up. Nothing in this package may be enabled on a production network. +// +// DO NOT ENABLE IN PRODUCTION. +package devtool + +import ( + tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/upgrade" + "github.com/urfave/cli" + + "morph-l2/node/flags" +) + +// startInSequencerModeFlag, when set, makes the node start directly in sequencer +// mode (skipping the pre-upgrade PBFT phase) by pre-setting the consensus upgrade +// block height to 0. Default is false, so production and ordinary devnet runs are +// unaffected. +// +// TEST/TESTNET-ONLY: never enable on a production network. +var startInSequencerModeFlag = cli.BoolFlag{ + Name: "startInSequencerMode", + Usage: "[TEST-ONLY] start directly in sequencer mode (pre-set upgrade block height = 0, skip the PBFT phase); never enable in production", + EnvVar: "MORPH_NODE_START_IN_SEQUENCER_MODE", +} + +// init self-registers the flag into the shared flag list so that cmd/node picks it +// up via `app.Flags = flags.Flags` without editing node/flags/flags.go. It runs +// because cmd/node imports this package to call ApplyStartInSequencerMode. +func init() { + flags.Flags = append(flags.Flags, startInSequencerModeFlag) +} + +// ApplyStartInSequencerMode optionally pre-sets the consensus upgrade block height +// to 0 so that IsUpgraded(1) == true and the node starts the sequencer routines +// directly, never entering the PBFT consensus reactor. Note the tendermint node +// itself still starts; only the pre-upgrade PBFT consensus phase is skipped. +// +// Ordering requirement: this MUST run before the upgrade store is wired (i.e. +// before SetupNode / node startup). At that point upgrade's store is still nil, +// so SetUpgradeBlockHeight only sets the in-memory value and does NOT persist it. +// On a fresh DB the later SetStore finds the key absent and keeps this value. +// Because this mode never enters PBFT, the height is never persisted, so every +// restart simply re-applies it here — making the switch idempotent across restarts +// (verified against upgrade.SetStore / SetUpgradeBlockHeight). +// +// TEST/TESTNET-ONLY. No-op unless --startInSequencerMode / +// MORPH_NODE_START_IN_SEQUENCER_MODE is set. +func ApplyStartInSequencerMode(ctx *cli.Context, logger tmlog.Logger) { + if !ctx.GlobalBool(startInSequencerModeFlag.Name) { + return + } + upgrade.SetUpgradeBlockHeight(0) + logger.Info("[TEST-ONLY] start-in-sequencer-mode: pre-set upgrade block height, starting directly in sequencer mode (PBFT phase skipped)", + "upgradeBlockHeight", upgrade.UpgradeBlockHeight()) +} diff --git a/ops/docker/docker-compose-cluster.yml b/ops/docker/docker-compose-cluster.yml index f5c75e6fb..3cc2089d8 100644 --- a/ops/docker/docker-compose-cluster.yml +++ b/ops/docker/docker-compose-cluster.yml @@ -92,6 +92,10 @@ services: environment: - MORPH_NODE_SEQUENCER_PRIVATE_KEY=${SEQUENCER_PRIVATE_KEY} - MORPH_NODE_SEQUENCER_UPGRADE_TIME=${SEQUENCER_UPGRADE_TIME:-0} + # TEST-ONLY: this HA cluster defaults to starting directly in sequencer mode + # (skip the PBFT phase). Set START_IN_SEQUENCER_MODE=false to instead run the + # normal PBFT->sequencer upgrade path. Never enable on a production network. + - MORPH_NODE_START_IN_SEQUENCER_MODE=${START_IN_SEQUENCER_MODE:-true} - MORPH_NODE_L2_ETH_RPC=http://ha-geth-0:8545 - MORPH_NODE_L2_ENGINE_RPC=http://ha-geth-0:8551 - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} @@ -132,6 +136,10 @@ services: environment: - MORPH_NODE_SEQUENCER_PRIVATE_KEY=${SEQUENCER_PRIVATE_KEY} - MORPH_NODE_SEQUENCER_UPGRADE_TIME=${SEQUENCER_UPGRADE_TIME:-0} + # TEST-ONLY: this HA cluster defaults to starting directly in sequencer mode + # (skip the PBFT phase). Set START_IN_SEQUENCER_MODE=false to instead run the + # normal PBFT->sequencer upgrade path. Never enable on a production network. + - MORPH_NODE_START_IN_SEQUENCER_MODE=${START_IN_SEQUENCER_MODE:-true} - MORPH_NODE_L2_ETH_RPC=http://ha-geth-1:8545 - MORPH_NODE_L2_ENGINE_RPC=http://ha-geth-1:8551 - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} @@ -172,6 +180,10 @@ services: environment: - MORPH_NODE_SEQUENCER_PRIVATE_KEY=${SEQUENCER_PRIVATE_KEY} - MORPH_NODE_SEQUENCER_UPGRADE_TIME=${SEQUENCER_UPGRADE_TIME:-0} + # TEST-ONLY: this HA cluster defaults to starting directly in sequencer mode + # (skip the PBFT phase). Set START_IN_SEQUENCER_MODE=false to instead run the + # normal PBFT->sequencer upgrade path. Never enable on a production network. + - MORPH_NODE_START_IN_SEQUENCER_MODE=${START_IN_SEQUENCER_MODE:-true} - MORPH_NODE_L2_ETH_RPC=http://ha-geth-2:8545 - MORPH_NODE_L2_ENGINE_RPC=http://ha-geth-2:8551 - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} diff --git a/ops/docker/docker-compose-devnet.yml b/ops/docker/docker-compose-devnet.yml index 451cd5e16..370ab44a6 100644 --- a/ops/docker/docker-compose-devnet.yml +++ b/ops/docker/docker-compose-devnet.yml @@ -185,6 +185,12 @@ services: - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} - MORPH_NODE_SEQUENCER_PRIVATE_KEY=${ACTIVE_SEQUENCER_PRIVATE_KEY} - MORPH_NODE_SEQUENCER_UPGRADE_TIME=${SEQUENCER_UPGRADE_TIME:-0} + # TEST-ONLY: make node-0 a local-verify follower of the start-in-sequencer-mode + # ha cluster. verify_mode=local drives P2P block-sync from the ha peers; born + # skips the PBFT/upgrade path so it matches the cluster's consensus mode. + # Never enable on a production network. + - MORPH_NODE_DERIVATION_VERIFY_MODE=local + - MORPH_NODE_START_IN_SEQUENCER_MODE=${START_IN_SEQUENCER_MODE:-true} volumes: - ".devnet/node0:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}"