Skip to content
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
38 changes: 32 additions & 6 deletions node/cmd/node/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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 == "" {
Expand Down
40 changes: 18 additions & 22 deletions node/derivation/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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))
Expand Down Expand Up @@ -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
}
25 changes: 16 additions & 9 deletions node/derivation/derivation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
}
Expand Down
15 changes: 0 additions & 15 deletions node/derivation/metrics.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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
}
31 changes: 0 additions & 31 deletions node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -420,9 +394,4 @@ var Flags = []cli.Flag{
LogFileMaxSize,
LogFileMaxAge,
LogCompress,

// metrics
MetricsServerEnable,
MetricsPort,
MetricsHostname,
}
6 changes: 6 additions & 0 deletions ops/docker/docker-compose-4nodes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down
Loading