diff --git a/go.work.sum b/go.work.sum index ef22362fd..7279f6a49 100644 --- a/go.work.sum +++ b/go.work.sum @@ -539,6 +539,7 @@ github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStB github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 h1:B2mpK+MNqgPqk2/KNi1LbqwtZDy5F7iy0mynQiBr8VA= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4/go.mod h1:y4GA2JbAUama1S4QwYjC2hefgGLU8Ul0GMtL/ADMF1c= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index 2f6206b5a..689e1884f 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "net/http" "os" "os/signal" "path/filepath" @@ -13,6 +14,7 @@ import ( "github.com/morph-l2/go-ethereum/common" "github.com/morph-l2/go-ethereum/crypto" "github.com/morph-l2/go-ethereum/ethclient" + "github.com/prometheus/client_golang/prometheus/promhttp" tmlog "github.com/tendermint/tendermint/libs/log" tmnode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/privval" @@ -143,13 +145,25 @@ func L2NodeMain(ctx *cli.Context) error { return err } - if isMockSequencer { + // ========== 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) if err != nil { return err } go ms.Start() - } else { + case derivationCfg.VerifyMode == derivation.VerifyModeLayer1: + nodeConfig.Logger.Info("layer1 verify mode: tendermint not started") + // Other modes inherit /metrics from tendermint; layer1 has to bring + // its own listener up on the same address. + startMetricsServer(tmCfg.Instrumentation.PrometheusListenAddr, nodeConfig.Logger) + default: // Convert typed nil (*HAService)(nil) to untyped nil interface to avoid // Go's nil interface gotcha: a typed nil satisfies (ha != nil) checks. var ha tmsequencer.SequencerHA @@ -172,10 +186,6 @@ func L2NodeMain(ctx *cli.Context) error { // is both redundant (it would re-fetch L1 batches it produced) and // unsafe (deriveForce would risk a self-reorg on transient divergence). if signer == nil { - derivationCfg := derivation.DefaultConfig() - if err := derivationCfg.SetCliContext(ctx); err != nil { - return fmt.Errorf("derivation set cli context error: %v", err) - } rollup, err := bindings.NewRollup(derivationCfg.RollupContractAddress, l1Client) if err != nil { return fmt.Errorf("NewRollup error: %v", err) @@ -360,6 +370,22 @@ func initL1SequencerComponents( return tracker, verifier, signer, nil } +func startMetricsServer(addr string, logger tmlog.Logger) { + if addr == "" { + logger.Info("metrics server disabled (instrumentation.prometheus_listen_addr is empty)") + return + } + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + srv := &http.Server{Addr: addr, Handler: mux} + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Error("metrics server", "addr", addr, "err", err) + } + }() + logger.Info("metrics server started", "addr", addr) +} + func homeDir(ctx *cli.Context) (string, error) { home := ctx.GlobalString(flags.Home.Name) if home == "" { diff --git a/node/derivation/config.go b/node/derivation/config.go index c70845478..64cde744d 100644 --- a/node/derivation/config.go +++ b/node/derivation/config.go @@ -21,8 +21,12 @@ import ( ) const ( - // DefaultFetchBlockRange is the number of blocks that we collect in a single eth_getLogs query. - DefaultFetchBlockRange = uint64(100) + // DefaultFetchBlockRange is the number of blocks that we collect in a + // single eth_getLogs query. 500 (vs sync.DefaultFetchBlockRange=100) + // trades RPC latency budget for fewer round-trips on the derivation + // path, where each query is only a CommitBatch event filter and the + // response stays small even at 500 blocks. + DefaultFetchBlockRange = uint64(500) // DefaultPollInterval is the frequency at which we query for new L1 messages. DefaultPollInterval = time.Second * 15 @@ -43,6 +47,11 @@ const ( // distance" rule of thumb and provides safety margin if Confirmations is // configured below finalized. DefaultReorgCheckDepth = uint64(64) + + // DefaultConfirmations: rationale lives on the L1.Confirmations field + // in DefaultConfig() — fixed-depth (latest-N) paired with the SPEC-005 + // §4.7.6 reorg detector, not a chain tag. + DefaultConfirmations = rpc.BlockNumber(10) ) // validateAndDefaultVerifyMode normalises an empty VerifyMode to the default @@ -72,24 +81,18 @@ type Config struct { FetchBlockRange uint64 `json:"fetch_block_range"` VerifyMode string `json:"verify_mode"` ReorgCheckDepth uint64 `json:"reorg_check_depth"` - MetricsPort uint64 `json:"metrics_port"` - MetricsHostname string `json:"metrics_hostname"` - MetricsServerEnable bool `json:"metrics_server_enable"` } func DefaultConfig() *Config { return &Config{ L1: &types.L1Config{ - // Default to L1 safe (~1 epoch / ~6 min lag) rather than finalized - // (~2 epochs / ~13 min lag). L1 safe blocks can theoretically be - // reorg'd if a Casper FFG slashing condition fires, so this default - // is paired with always-on L1 reorg detection (SPEC-005 §4.7.6 in - // reorg.go) which rewinds the derivation cursor and resets the tag - // advancer when an L1 hash mismatch is observed. Operators wanting - // strict no-reorg-possible reads can still set - // --derivation.confirmations=-3 (rpc.FinalizedBlockNumber) or - // --l1.confirmations=-3 to revert to the previous behavior. - Confirmations: rpc.SafeBlockNumber, + // Fixed-depth (latest-N) confirmations rather than the SafeBlockNumber + // tag: 10 blocks (~2 min on mainnet) keeps lag low, and the always-on + // L1 reorg detector (SPEC-005 §4.7.6 in reorg.go) rewinds the + // derivation cursor on hash mismatch so a deeper reorg is recoverable. + // Operators wanting strict no-reorg-possible reads can still set + // --derivation.confirmations=-3 (rpc.FinalizedBlockNumber). + Confirmations: DefaultConfirmations, }, PollInterval: DefaultPollInterval, LogProgressInterval: DefaultLogProgressInterval, @@ -102,9 +105,6 @@ func DefaultConfig() *Config { func (c *Config) SetCliContext(ctx *cli.Context) error { c.L1.Addr = ctx.GlobalString(flags.L1NodeAddr.Name) - if ctx.GlobalIsSet(flags.L1Confirmations.Name) { - c.L1.Confirmations = rpc.BlockNumber(ctx.GlobalInt64(flags.L1Confirmations.Name)) - } // The current setting priority is greater than Env L1Confirmations if ctx.GlobalIsSet(flags.DerivationConfirmations.Name) { c.L1.Confirmations = rpc.BlockNumber(ctx.GlobalInt64(flags.DerivationConfirmations.Name)) @@ -198,9 +198,5 @@ func (c *Config) SetCliContext(ctx *cli.Context) error { c.L2.EngineAddr = l2EngineAddr c.L2.JwtSecret = secret - c.MetricsServerEnable = ctx.GlobalBool(flags.MetricsServerEnable.Name) - c.MetricsHostname = ctx.GlobalString(flags.MetricsHostname.Name) - c.MetricsPort = ctx.GlobalUint64(flags.MetricsPort.Name) - return nil } diff --git a/node/derivation/derivation.go b/node/derivation/derivation.go index 818d5467a..7147ae97e 100644 --- a/node/derivation/derivation.go +++ b/node/derivation/derivation.go @@ -119,16 +119,10 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer, } ctx, cancel := context.WithCancel(ctx) logger = logger.With("module", "derivation") + // Metrics register to prometheus.DefaultRegisterer; the HTTP endpoint + // itself is started once at the top level (cmd/node/main.go) so every + // verify-mode and sequencer-mode produces exactly one /metrics URL. metrics := PrometheusMetrics("morphnode") - if cfg.MetricsServerEnable { - go func() { - _, err := metrics.Serve(cfg.MetricsHostname, cfg.MetricsPort) - if err != nil { - panic(fmt.Errorf("metrics server start error:%v", err)) - } - }() - logger.Info("metrics server enabled", "host", cfg.MetricsHostname, "port", cfg.MetricsPort) - } baseHttp := NewBasicHTTPClient(cfg.BeaconRpc, logger) l1BeaconClient := NewL1BeaconClient(baseHttp) @@ -176,6 +170,19 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer, logger.Info("derivation startHeight defaulted to latest L1 confirmed block", "height", blockNumber, "confirmations", d.confirmations) d.startHeight = blockNumber } + // First-run baseHeight default: baseHeight is the L2 height below which + // stateRoot checks are skipped (snapshot-imported nodes set this to the + // snapshot height). When unset, pin to the current L2 head so derivation + // only verifies blocks it actually produces from this point forward — + // otherwise it would re-verify historical blocks against an empty rollup + // cursor and fail. + if d.baseHeight == 0 { + l2Number, err := d.l2Client.BlockNumber(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch l2 block number: %w", err) + } + d.baseHeight = l2Number + } return d, nil } diff --git a/node/derivation/metrics.go b/node/derivation/metrics.go index 285525157..dce79ca13 100644 --- a/node/derivation/metrics.go +++ b/node/derivation/metrics.go @@ -1,14 +1,9 @@ package derivation import ( - "net" - "net/http" - "strconv" - "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( @@ -181,13 +176,3 @@ func (m *Metrics) IncL1ReorgReset() { func (m *Metrics) IncTagInvariantViolation() { m.TagInvariantViolationTotal.Add(1) } - -func (m *Metrics) Serve(hostname string, port uint64) (*http.Server, error) { - mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - srv := new(http.Server) - srv.Addr = net.JoinHostPort(hostname, strconv.FormatUint(port, 10)) - srv.Handler = mux - err := srv.ListenAndServe() - return srv, err -} diff --git a/node/flags/flags.go b/node/flags/flags.go index 124353034..488295338 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -70,12 +70,6 @@ var ( EnvVar: prefixEnvVar("L1_ETH_BEACON_RPC"), } - L1ChainID = cli.Uint64Flag{ - Name: "l1.chain-id", - Usage: "L1 Chain ID", - EnvVar: prefixEnvVar("L1_CHAIN_ID"), - } - L1Confirmations = cli.Int64Flag{ Name: "l1.confirmations", Usage: "Number of confirmations on L1 needed for finalization", @@ -334,31 +328,11 @@ var ( Usage: "Compress determines if the rotated log files should be compressed using gzip. The default is not to perform compression. It is used only when log.filename is provided.", EnvVar: prefixEnvVar("LOG_COMPRESS"), } - - // metrics - MetricsServerEnable = cli.BoolFlag{ - Name: "metrics-server-enable", - Usage: "Whether or not to run the embedded metrics server", - EnvVar: prefixEnvVar("METRICS_SERVER_ENABLE"), - } - MetricsHostname = cli.StringFlag{ - Name: "metrics-hostname", - Usage: "The hostname of the metrics server", - Value: "0.0.0.0", - EnvVar: prefixEnvVar("METRICS_HOSTNAME"), - } - MetricsPort = cli.Uint64Flag{ - Name: "metrics-port", - Usage: "The port of the metrics server", - Value: 26660, - EnvVar: prefixEnvVar("METRICS_PORT"), - } ) var Flags = []cli.Flag{ Home, L1NodeAddr, - L1ChainID, L1Confirmations, L2EthAddr, L2EngineAddr, @@ -420,9 +394,4 @@ var Flags = []cli.Flag{ LogFileMaxSize, LogFileMaxAge, LogCompress, - - // metrics - MetricsServerEnable, - MetricsPort, - MetricsHostname, } diff --git a/ops/docker/docker-compose-4nodes.yml b/ops/docker/docker-compose-4nodes.yml index 9b3934d14..cb7de2473 100644 --- a/ops/docker/docker-compose-4nodes.yml +++ b/ops/docker/docker-compose-4nodes.yml @@ -241,6 +241,7 @@ services: - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_UPGRADE_BATCH_TIME=${BATCH_UPGRADE_TIME} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node0:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}" @@ -271,6 +272,7 @@ services: - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_UPGRADE_BATCH_TIME=${BATCH_UPGRADE_TIME} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node1:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}" @@ -302,6 +304,7 @@ services: - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_UPGRADE_BATCH_TIME=${BATCH_UPGRADE_TIME} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node2:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}" @@ -333,6 +336,7 @@ services: - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_UPGRADE_BATCH_TIME=${BATCH_UPGRADE_TIME} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node3:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}" @@ -388,6 +392,7 @@ services: - MORPH_NODE_L1_CONFIRMATIONS=0 - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node4:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}" @@ -443,6 +448,7 @@ services: - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_DERIVATION_VERIFY_MODE=layer1 + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} volumes: - ".devnet/node5:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}"