diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index ddf7f728c..e96af5341 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -15,6 +15,7 @@ import ( "github.com/morph-l2/go-ethereum/crypto" "github.com/morph-l2/go-ethereum/ethclient" "github.com/prometheus/client_golang/prometheus/promhttp" + tmcrypto "github.com/tendermint/tendermint/crypto" tmlog "github.com/tendermint/tendermint/libs/log" tmnode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/privval" @@ -131,8 +132,30 @@ func L2NodeMain(ctx *cli.Context) error { if err != nil { return err } - tmVal := privval.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()) - pubKey, _ := tmVal.GetPubKey() + + // Load derivation config here (not just before the switch below): its + // VerifyMode decides whether this node runs the Tendermint consensus node, + // which in turn decides whether we need a priv-validator file. + derivationCfg := derivation.DefaultConfig() + if err := derivationCfg.SetCliContext(ctx); err != nil { + return fmt.Errorf("derivation set cli context error: %v", err) + } + + // Only nodes that start the Tendermint consensus node need a priv-validator + // key/state file. Layer1-verify validators never start Tendermint (see the + // switch below); LoadOrGenFilePV would generate and Save a FilePV, forcing a + // /data/ directory that from-scratch validators never had and panicking + // when it is absent. Skip it and hand the executor a nil pubkey -- NewExecutor + // and the sequencer-set check both tolerate nil (a layer1 validator is never + // in the sequencer set, so its "am I a sequencer" check is correctly false). + var ( + tmVal *privval.FilePV + pubKey tmcrypto.PubKey + ) + if derivationCfg.VerifyMode != derivation.VerifyModeLayer1 { + tmVal = privval.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()) + pubKey, _ = tmVal.GetPubKey() + } // Reuse the shared syncer instance -- DevSequencer mode is the only path // that pulls a syncer out of NewExecutor, so we hand back the same one @@ -158,12 +181,6 @@ func L2NodeMain(ctx *cli.Context) error { return err } - // ========== Derivation config (loaded early to drive the layer1 branch below) ========== - derivationCfg := derivation.DefaultConfig() - if err := derivationCfg.SetCliContext(ctx); err != nil { - return fmt.Errorf("derivation set cli context error: %v", err) - } - switch { case isMockSequencer: ms, err = mock.NewSequencer(executor) diff --git a/node/derivation/config.go b/node/derivation/config.go index 2f73092ce..29b6f9a39 100644 --- a/node/derivation/config.go +++ b/node/derivation/config.go @@ -170,6 +170,14 @@ func (c *Config) SetCliContext(ctx *cli.Context) error { } c.VerifyMode = normalized + // Layer1-verify validators historically derived only from finalized L1 data + // (the pre-centralized-sequencer default). Preserve that: unless the operator + // explicitly set --derivation.confirmations, a layer1 node reads finalized + // rather than the fixed-depth latest-N default that consensus fullnodes use. + if c.VerifyMode == VerifyModeLayer1 && !ctx.GlobalIsSet(flags.DerivationConfirmations.Name) { + c.L1.Confirmations = rpc.FinalizedBlockNumber + } + if ctx.GlobalIsSet(flags.DerivationReorgCheckDepth.Name) { c.ReorgCheckDepth = ctx.GlobalUint64(flags.DerivationReorgCheckDepth.Name) }