Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2cdfb07
update l2 RetryableClient
Dec 11, 2025
7c11f8c
fmt
Dec 11, 2025
98925c5
clean
Dec 11, 2025
c34bee9
fix mpt time
Dec 11, 2025
e39a59a
fix
Dec 15, 2025
105c335
Merge branch 'main' into mpt-switch
curryxbo Dec 15, 2025
a3d76f2
clean
Dec 17, 2025
8ae80bd
fix and test
Dec 22, 2025
d5f2d8d
clean
Dec 22, 2025
fa2307f
update
Jan 9, 2026
c38ea30
refactor
Jan 12, 2026
8b2a833
chore: remove mpt-switch-test from git tracking
Jan 12, 2026
c9f508d
chore: revert go-ethereum submodule to previous version
Jan 12, 2026
0968304
fix
Jan 12, 2026
f4d7b0c
rollback skip bls verify
Jan 13, 2026
32512d0
fix
Jan 13, 2026
b5e977c
clean
Jan 13, 2026
5f6ad17
clean
Jan 13, 2026
8631ca8
Merge branch 'main' into mpt-switch
Jan 13, 2026
5dea362
update geth version
Jan 13, 2026
772df41
Fix NewRetryableClient (#856)
curryxbo Jan 15, 2026
75181a7
Fix swtich time (#862)
curryxbo Jan 28, 2026
6120b0a
add mpt upgrade code for batch finalize
Kukoomomo Jan 29, 2026
89218d0
fix log level
Jan 29, 2026
0a63c6b
fix log level
Jan 29, 2026
a438b20
Force batch points around MPT fork to isolate the first post-fork blo…
FletcherMan Jan 30, 2026
8abcf7c
update submitter mpt config
Kukoomomo Jan 30, 2026
d6dd008
Merge branch 'mpt-switch' of github.com:morph-l2/morph into mpt-switch
Kukoomomo Jan 30, 2026
abcc805
update submitter mpt config
Kukoomomo Jan 30, 2026
57cf1e4
add bls key fork height flag (#863)
FletcherMan Feb 3, 2026
0f74ddb
Add retry for fetch eth_config (#865)
curryxbo Feb 4, 2026
0ec7bda
Merge branch 'main' into mpt-switch
curryxbo Feb 6, 2026
fd31a72
update geth version
Feb 6, 2026
fae4ad8
clean unused code
Kukoomomo Feb 9, 2026
ffa5e6d
Merge branch 'mpt-switch' of github.com:morph-l2/morph into mpt-switch
Kukoomomo Feb 9, 2026
f11e92a
Bump version and fix lint (#881)
curryxbo Feb 11, 2026
f34a133
make submodules
curryxbo Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ contracts/mainnet.json
.env

# logs
*.log
*.log

# mpt-switch-test (local testing only)
ops/mpt-switch-test
2 changes: 1 addition & 1 deletion bindings/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24.0

replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.2

require github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24
require github.com/morph-l2/go-ethereum v1.10.14-0.20251211075654-796834acba86

require (
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
Expand Down
3 changes: 1 addition & 2 deletions bindings/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U=
github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M=
github.com/morph-l2/go-ethereum v1.10.14-0.20251211075654-796834acba86 h1:4BgRnW5lZcgtVvK/WuDTNAfi5F5/VEb7FbDEvCksPHk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down
2 changes: 1 addition & 1 deletion contracts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.

require (
github.com/iden3/go-iden3-crypto v0.0.16
github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24
github.com/morph-l2/go-ethereum v1.10.14-0.20251211075654-796834acba86
github.com/stretchr/testify v1.10.0
)

Expand Down
3 changes: 1 addition & 2 deletions contracts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U=
github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M=
github.com/morph-l2/go-ethereum v1.10.14-0.20251211075654-796834acba86 h1:4BgRnW5lZcgtVvK/WuDTNAfi5F5/VEb7FbDEvCksPHk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
Expand Down
12 changes: 12 additions & 0 deletions node/core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (

type Config struct {
L2 *types.L2Config `json:"l2"`
L2Next *types.L2Config `json:"l2_next,omitempty"` // optional, for geth upgrade switch
L2CrossDomainMessengerAddress common.Address `json:"cross_domain_messenger_address"`
SequencerAddress common.Address `json:"sequencer_address"`
GovAddress common.Address `json:"gov_address"`
Expand All @@ -42,6 +43,7 @@ type Config struct {
func DefaultConfig() *Config {
return &Config{
L2: new(types.L2Config),
L2Next: nil, // optional, only for upgrade switch
Logger: tmlog.NewTMLogger(tmlog.NewSyncWriter(os.Stdout)),
MaxL1MessageNumPerBlock: 100,
L2CrossDomainMessengerAddress: predeploys.L2CrossDomainMessengerAddr,
Expand Down Expand Up @@ -122,6 +124,16 @@ func (c *Config) SetCliContext(ctx *cli.Context) error {
c.L2.EngineAddr = l2EngineAddr
c.L2.JwtSecret = secret

// L2Next is optional - only for upgrade switch (e.g., ZK to MPT)
l2NextEthAddr := ctx.GlobalString(flags.L2NextEthAddr.Name)
l2NextEngineAddr := ctx.GlobalString(flags.L2NextEngineAddr.Name)
if l2NextEthAddr != "" && l2NextEngineAddr != "" {
c.L2Next = &types.L2Config{
EthAddr: l2NextEthAddr,
EngineAddr: l2NextEngineAddr,
JwtSecret: secret, // same secret
}
}
if ctx.GlobalIsSet(flags.MaxL1MessageNumPerBlock.Name) {
c.MaxL1MessageNumPerBlock = ctx.GlobalUint64(flags.MaxL1MessageNumPerBlock.Name)
if c.MaxL1MessageNumPerBlock == 0 {
Expand Down
61 changes: 55 additions & 6 deletions node/core/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func getNextL1MsgIndex(client *types.RetryableClient) (uint64, error) {
func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubKey) (*Executor, error) {
logger := config.Logger
logger = logger.With("module", "executor")
// L2 geth endpoint (required - current geth)
aClient, err := authclient.DialContext(context.Background(), config.L2.EngineAddr, config.L2.JwtSecret)
if err != nil {
return nil, err
Expand All @@ -79,7 +80,24 @@ func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubK
return nil, err
}

l2Client := types.NewRetryableClient(aClient, eClient, config.Logger)
// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if config.L2Next != nil && config.L2Next.EngineAddr != "" && config.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), config.L2Next.EngineAddr, config.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(config.L2Next.EthAddr)
if err != nil {
return nil, err
}
Comment on lines +91 to +102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Close aNextClient when eNextClient dial fails.

Line 100 returns on error but leaves the auth client open.

🧹 Proposed fix
eNextClient, err = ethclient.Dial(config.L2Next.EthAddr)
if err != nil {
+	aNextClient.Close()
	return nil, err
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if config.L2Next != nil && config.L2Next.EngineAddr != "" && config.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), config.L2Next.EngineAddr, config.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(config.L2Next.EthAddr)
if err != nil {
return nil, err
}
// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if config.L2Next != nil && config.L2Next.EngineAddr != "" && config.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), config.L2Next.EngineAddr, config.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(config.L2Next.EthAddr)
if err != nil {
aNextClient.Close()
return nil, err
}
🤖 Prompt for AI Agents
In `@node/core/executor.go` around lines 91 - 102, When dialing the L2Next
clients, if authclient.DialContext succeeds (aNextClient is non-nil) but
ethclient.Dial (eNextClient) fails, ensure you close the opened aNextClient
before returning the error; update the block that calls authclient.DialContext
and ethclient.Dial in executor.go to call aNextClient.Close() (or the
appropriate Close/Disconnect method on authclient.Client) just prior to
returning the error from ethclient.Dial so the auth client is not leaked.

logger.Info("L2Next geth configured (upgrade switch enabled)", "engineAddr", config.L2Next.EngineAddr, "ethAddr", config.L2Next.EthAddr)
} else {
logger.Info("L2Next geth not configured (no upgrade switch)")
}

l2Client := types.NewRetryableClient(aClient, eClient, aNextClient, eNextClient, config.L2.EthAddr, config.Logger)
index, err := getNextL1MsgIndex(l2Client)
if err != nil {
return nil, err
Expand Down Expand Up @@ -276,16 +294,39 @@ func (e *Executor) DeliverBlock(txs [][]byte, metaData []byte, consensusData l2n
}

if wrappedBlock.Number <= height {
e.logger.Info("ignore it, the block was delivered", "block number", wrappedBlock.Number)
if e.devSequencer {
return nil, consensusData.ValidatorSet, nil
e.logger.Info("block already delivered by geth (via P2P sync)", "block_number", wrappedBlock.Number)
// Even if block was already delivered (e.g., synced via P2P), we still need to check
// if MPT switch should happen, otherwise sentry nodes won't switch to the correct geth.
e.l2Client.EnsureSwitched(context.Background(), wrappedBlock.Timestamp, wrappedBlock.Number)

// After switch, re-check height from the new geth client
// The block might exist in legacy geth but not in target geth after switch
newHeight, err := e.l2Client.BlockNumber(context.Background())
if err != nil {
return nil, nil, err
}
if wrappedBlock.Number > newHeight {
e.logger.Info("block not in target geth after switch, need to deliver",
"block_number", wrappedBlock.Number,
"old_height", height,
"new_height", newHeight)
// Update height and continue to deliver the block
height = newHeight
} else {
if e.devSequencer {
return nil, consensusData.ValidatorSet, nil
}
return e.getParamsAndValsAtHeight(int64(wrappedBlock.Number))
}
Comment thread
curryxbo marked this conversation as resolved.
return e.getParamsAndValsAtHeight(int64(wrappedBlock.Number))
}

// We only accept the continuous blocks for now.
// It acts like full sync. Snap sync is not enabled until the Geth enables snapshot with zkTrie
if wrappedBlock.Number > height+1 {
e.logger.Error("!!! CRITICAL: Geth is behind - node BLOCKED !!!",
"consensus_block", wrappedBlock.Number,
"geth_height", height,
"action", "Switch to MPT-compatible geth IMMEDIATELY")
return nil, nil, types.ErrWrongBlockNumber
}

Expand Down Expand Up @@ -317,7 +358,15 @@ func (e *Executor) DeliverBlock(txs [][]byte, metaData []byte, consensusData l2n
}
err = e.l2Client.NewL2Block(context.Background(), l2Block, batchHash)
if err != nil {
e.logger.Error("failed to NewL2Block", "error", err)
e.logger.Error("========================================")
e.logger.Error("CRITICAL: Failed to deliver block to geth!")
e.logger.Error("========================================")
e.logger.Error("failed to NewL2Block",
"error", err,
"block_number", l2Block.Number,
"block_timestamp", l2Block.Timestamp)
e.logger.Error("HINT: If this occurs after MPT upgrade, your geth node may not support MPT blocks. " +
"Please ensure you are running an MPT-compatible geth node.")
return nil, nil, err
}

Expand Down
4 changes: 4 additions & 0 deletions node/core/sequencers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (e *Executor) VerifySignature(tmPubKey []byte, messageHash []byte, blsSig [
e.logger.Info("we are in dev mode, do not verify the bls signature")
return true, nil
}
if !e.isSequencer {
e.logger.Debug("non-sequencer node, skip bls signature verification")
return true, nil
}
if len(e.valsByTmKey) == 0 {
return false, errors.New("no available sequencers found in layer2")
}
Expand Down
13 changes: 13 additions & 0 deletions node/derivation/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
type Config struct {
L1 *types.L1Config `json:"l1"`
L2 *types.L2Config `json:"l2"`
L2Next *types.L2Config `json:"l2_next,omitempty"` // optional, for geth upgrade switch
BeaconRpc string `json:"beacon_rpc"`
RollupContractAddress common.Address `json:"rollup_contract_address"`
StartHeight uint64 `json:"start_height"`
Expand All @@ -55,6 +56,7 @@ func DefaultConfig() *Config {
LogProgressInterval: DefaultLogProgressInterval,
FetchBlockRange: DefaultFetchBlockRange,
L2: new(types.L2Config),
L2Next: nil, // optional, only for upgrade switch
}
}

Expand Down Expand Up @@ -135,6 +137,17 @@ func (c *Config) SetCliContext(ctx *cli.Context) error {
c.L2.EthAddr = l2EthAddr
c.L2.EngineAddr = l2EngineAddr
c.L2.JwtSecret = secret

// L2Next is optional - only for upgrade switch (e.g., ZK to MPT)
l2NextEthAddr := ctx.GlobalString(flags.L2NextEthAddr.Name)
l2NextEngineAddr := ctx.GlobalString(flags.L2NextEngineAddr.Name)
if l2NextEthAddr != "" && l2NextEngineAddr != "" {
c.L2Next = &types.L2Config{
EthAddr: l2NextEthAddr,
EngineAddr: l2NextEngineAddr,
JwtSecret: secret, // same secret
}
}
c.MetricsServerEnable = ctx.GlobalBool(flags.MetricsServerEnable.Name)
c.MetricsHostname = ctx.GlobalString(flags.MetricsHostname.Name)
c.MetricsPort = ctx.GlobalUint64(flags.MetricsPort.Name)
Expand Down
90 changes: 72 additions & 18 deletions node/derivation/derivation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"math/big"
"os"
"time"

"github.com/morph-l2/go-ethereum"
Expand Down Expand Up @@ -64,6 +63,10 @@ type Derivation struct {
pollInterval time.Duration
logProgressInterval time.Duration
stop chan struct{}

// geth upgrade config (fetched once at startup)
switchTime uint64
useZktrie bool
}

type DeployContractBackend interface {
Expand All @@ -78,6 +81,7 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer,
if err != nil {
return nil, err
}
// L2 geth endpoint (required - current geth)
aClient, err := authclient.DialContext(context.Background(), cfg.L2.EngineAddr, cfg.L2.JwtSecret)
if err != nil {
return nil, err
Expand All @@ -86,6 +90,24 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer,
if err != nil {
return nil, err
}

// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if cfg.L2Next != nil && cfg.L2Next.EngineAddr != "" && cfg.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), cfg.L2Next.EngineAddr, cfg.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(cfg.L2Next.EthAddr)
if err != nil {
return nil, err
}
logger.Info("L2Next geth configured (upgrade switch enabled)", "engineAddr", cfg.L2Next.EngineAddr, "ethAddr", cfg.L2Next.EthAddr)
} else {
logger.Info("L2Next geth not configured (no upgrade switch)")
}
Comment on lines +93 to +109

@coderabbitai coderabbitai Bot Jan 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Resource leak on error path.

If eNextClient dial fails at line 102, the already-created aNextClient is never closed. Consider using defer-based cleanup or explicit cleanup on error paths.

Suggested fix pattern
 	// L2Next endpoint (optional - for upgrade switch)
 	var aNextClient *authclient.Client
 	var eNextClient *ethclient.Client
 	if cfg.L2Next != nil && cfg.L2Next.EngineAddr != "" && cfg.L2Next.EthAddr != "" {
 		aNextClient, err = authclient.DialContext(context.Background(), cfg.L2Next.EngineAddr, cfg.L2Next.JwtSecret)
 		if err != nil {
 			return nil, err
 		}
 		eNextClient, err = ethclient.Dial(cfg.L2Next.EthAddr)
 		if err != nil {
+			aNextClient.Close()
 			return nil, err
 		}
 		logger.Info("L2Next geth configured (upgrade switch enabled)", "engineAddr", cfg.L2Next.EngineAddr, "ethAddr", cfg.L2Next.EthAddr)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if cfg.L2Next != nil && cfg.L2Next.EngineAddr != "" && cfg.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), cfg.L2Next.EngineAddr, cfg.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(cfg.L2Next.EthAddr)
if err != nil {
return nil, err
}
logger.Info("L2Next geth configured (upgrade switch enabled)", "engineAddr", cfg.L2Next.EngineAddr, "ethAddr", cfg.L2Next.EthAddr)
} else {
logger.Info("L2Next geth not configured (no upgrade switch)")
}
// L2Next endpoint (optional - for upgrade switch)
var aNextClient *authclient.Client
var eNextClient *ethclient.Client
if cfg.L2Next != nil && cfg.L2Next.EngineAddr != "" && cfg.L2Next.EthAddr != "" {
aNextClient, err = authclient.DialContext(context.Background(), cfg.L2Next.EngineAddr, cfg.L2Next.JwtSecret)
if err != nil {
return nil, err
}
eNextClient, err = ethclient.Dial(cfg.L2Next.EthAddr)
if err != nil {
aNextClient.Close()
return nil, err
}
logger.Info("L2Next geth configured (upgrade switch enabled)", "engineAddr", cfg.L2Next.EngineAddr, "ethAddr", cfg.L2Next.EthAddr)
} else {
logger.Info("L2Next geth not configured (no upgrade switch)")
}
🤖 Prompt for AI Agents
In @node/derivation/derivation.go around lines 93 - 109, The aNextClient created
by authclient.DialContext can leak if ethclient.Dial for eNextClient fails;
after authclient.DialContext returns aNextClient successfully (in the L2Next
block), either defer aNextClient.Close() immediately or explicitly call
aNextClient.Close() before returning the error from ethclient.Dial so the client
is always closed on the error path; update the L2Next initialization in
derivation.go to ensure aNextClient is cleaned up when ethclient.Dial fails
(referencing aNextClient, eNextClient, authclient.DialContext and
ethclient.Dial).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is process startup initialization. If dial fails, the process exits immediately and OS reclaims all resources. No actual leak occurs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@curryxbo, you're right—in a startup initialization path where the entire process would exit on error, the OS will reclaim the connection resources. The practical impact is minimal in this specific context. Thanks for the clarification!


msgPasser, err := bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, eClient)
if err != nil {
return nil, err
Expand Down Expand Up @@ -116,6 +138,14 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer,
}
baseHttp := NewBasicHTTPClient(cfg.BeaconRpc, logger)
l1BeaconClient := NewL1BeaconClient(baseHttp)

// Fetch geth config once at startup for root validation skip logic
gethCfg, err := types.FetchGethConfig(cfg.L2.EthAddr, logger)
if err != nil {
return nil, fmt.Errorf("failed to fetch geth config: %w", err)
}
logger.Info("Geth config fetched", "switchTime", gethCfg.SwitchTime, "useZktrie", gethCfg.UseZktrie)

return &Derivation{
ctx: ctx,
db: db,
Expand All @@ -129,7 +159,7 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer,
logger: logger,
RollupContractAddress: cfg.RollupContractAddress,
confirmations: cfg.L1.Confirmations,
l2Client: types.NewRetryableClient(aClient, eClient, tmlog.NewTMLogger(tmlog.NewSyncWriter(os.Stdout))),
l2Client: types.NewRetryableClient(aClient, eClient, aNextClient, eNextClient, cfg.L2.EthAddr, logger),
cancel: cancel,
stop: make(chan struct{}),
startHeight: cfg.StartHeight,
Expand All @@ -140,6 +170,8 @@ func NewDerivationClient(ctx context.Context, cfg *Config, syncer *sync.Syncer,
metrics: metrics,
l1BeaconClient: l1BeaconClient,
L2ToL1MessagePasser: msgPasser,
switchTime: gethCfg.SwitchTime,
useZktrie: gethCfg.UseZktrie,
}, nil
}

Expand Down Expand Up @@ -246,25 +278,47 @@ func (d *Derivation) derivationBlock(ctx context.Context) {
d.logger.Error("get withdrawal root failed", "error", err)
return
}
if !bytes.Equal(lastHeader.Root.Bytes(), batchInfo.root.Bytes()) || !bytes.Equal(withdrawalRoot[:], batchInfo.withdrawalRoot.Bytes()) {
d.metrics.SetBatchStatus(stateException)
// TODO The challenge switch is currently on and will be turned on in the future
if d.validator != nil && d.validator.ChallengeEnable() {
if err := d.validator.ChallengeState(batchInfo.batchIndex); err != nil {
d.logger.Error("challenge state failed")
return

rootMismatch := !bytes.Equal(lastHeader.Root.Bytes(), batchInfo.root.Bytes())
withdrawalMismatch := !bytes.Equal(withdrawalRoot[:], batchInfo.withdrawalRoot.Bytes())

if rootMismatch || withdrawalMismatch {
// Check if should skip validation during upgrade transition
// Skip if: (before switch && MPT geth) or (after switch && ZK geth)
skipValidation := false
if d.switchTime > 0 {
beforeSwitch := lastHeader.Time < d.switchTime
if (beforeSwitch && !d.useZktrie) || (!beforeSwitch && d.useZktrie) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
skipValidation = true
d.logger.Error("Root validation skipped during upgrade transition",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

d.logger.Info("xxx")

"originStateRootHash", batchInfo.root,
"deriveStateRootHash", lastHeader.Root.Hex(),
"blockTimestamp", lastHeader.Time,
"switchTime", d.switchTime,
"useZktrie", d.useZktrie,
)
}
}
d.logger.Info("root hash or withdrawal hash is not equal",
"originStateRootHash", batchInfo.root,
"deriveStateRootHash", lastHeader.Root.Hex(),
"batchWithdrawalRoot", batchInfo.withdrawalRoot.Hex(),
"deriveWithdrawalRoot", common.BytesToHash(withdrawalRoot[:]).Hex(),
)
return
} else {
d.metrics.SetBatchStatus(stateNormal)

if !skipValidation {
d.metrics.SetBatchStatus(stateException)
// TODO The challenge switch is currently on and will be turned on in the future
if d.validator != nil && d.validator.ChallengeEnable() {
if err := d.validator.ChallengeState(batchInfo.batchIndex); err != nil {
d.logger.Error("challenge state failed")
return
}
}
d.logger.Info("root hash or withdrawal hash is not equal",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

root hash not equal should d.logger.Error("xxx")

"originStateRootHash", batchInfo.root,
"deriveStateRootHash", lastHeader.Root.Hex(),
"batchWithdrawalRoot", batchInfo.withdrawalRoot.Hex(),
"deriveWithdrawalRoot", common.BytesToHash(withdrawalRoot[:]).Hex(),
)
return
}
}
d.metrics.SetBatchStatus(stateNormal)
d.metrics.SetL1SyncHeight(lg.BlockNumber)
}

Expand Down
14 changes: 14 additions & 0 deletions node/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ var (
EnvVar: prefixEnvVar("L2_ENGINE_RPC"),
}

L2NextEthAddr = cli.StringFlag{
Name: "l2next.eth",
Usage: "Address of next L2 geth JSON-RPC endpoints to switch to (optional, for upgrades)",
EnvVar: prefixEnvVar("L2_NEXT_ETH_RPC"),
}

L2NextEngineAddr = cli.StringFlag{
Name: "l2next.engine",
Usage: "Address of next L2 geth Engine JSON-RPC endpoints to switch to (optional, for upgrades)",
EnvVar: prefixEnvVar("L2_NEXT_ENGINE_RPC"),
}

L2EngineJWTSecret = cli.StringFlag{
Name: "l2.jwt-secret",
Usage: "Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty.",
Expand Down Expand Up @@ -304,6 +316,8 @@ var Flags = []cli.Flag{
L2EthAddr,
L2EngineAddr,
L2EngineJWTSecret,
L2NextEthAddr,
L2NextEngineAddr,
MaxL1MessageNumPerBlock,
L2CrossDomainMessengerContractAddr,
L2SequencerAddr,
Expand Down
Loading