From eba200cfb82dba473d8b791064c6b32bdb539ea4 Mon Sep 17 00:00:00 2001 From: "fletcher.fan" Date: Mon, 15 Dec 2025 15:21:29 +0800 Subject: [PATCH 1/3] fix(node): skip blsKey check before fork height for historical compatibility --- node/core/executor.go | 22 ++++++++++++++++++---- node/core/sequencers.go | 18 +++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/node/core/executor.go b/node/core/executor.go index 90f97e253..5818adf72 100644 --- a/node/core/executor.go +++ b/node/core/executor.go @@ -135,7 +135,12 @@ func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubK return executor, nil } - if _, err = executor.updateSequencerSet(); err != nil { + // Get current height for initial sequencer set update + currentHeight, err := l2Client.BlockNumber(context.Background()) + if err != nil { + return nil, err + } + if _, err = executor.updateSequencerSet(currentHeight); err != nil { return nil, err } @@ -327,7 +332,7 @@ func (e *Executor) DeliverBlock(txs [][]byte, metaData []byte, consensusData l2n var newValidatorSet = consensusData.ValidatorSet var newBatchParams *tmproto.BatchParams if !e.devSequencer { - if newValidatorSet, err = e.updateSequencerSet(); err != nil { + if newValidatorSet, err = e.updateSequencerSet(l2Block.Number); err != nil { return nil, nil, err } if newBatchParams, err = e.batchParamsUpdates(l2Block.Number); err != nil { @@ -390,9 +395,18 @@ func (e *Executor) getParamsAndValsAtHeight(height int64) (*tmproto.BatchParams, if err != nil { return nil, nil, err } - newValidators := make([][]byte, len(addrs)) + newValidators := make([][]byte, 0, len(addrs)) for i := range stakesInfo { - newValidators[i] = stakesInfo[i].TmKey[:] + // validate blsKey to keep consistent with sequencerSetUpdates + if _, err := decodeBlsPubKey(stakesInfo[i].BlsKey); err != nil { + e.logger.Error("getParamsAndValsAtHeight: failed to decode bls key", "key bytes", hexutil.Encode(stakesInfo[i].BlsKey), "error", err) + // Before blsKeyCheckForkHeight (inclusive), include sequencers with invalid blsKey + // to maintain compatibility with historical blocks + if height > blsKeyCheckForkHeight { + continue + } + } + newValidators = append(newValidators, stakesInfo[i].TmKey[:]) } return &tmproto.BatchParams{ diff --git a/node/core/sequencers.go b/node/core/sequencers.go index 871994300..3d4bb0d65 100644 --- a/node/core/sequencers.go +++ b/node/core/sequencers.go @@ -18,6 +18,11 @@ import ( const tmKeySize = ed25519.PubKeySize +// blsKeyCheckForkHeight is the height at which blsKey validation was enforced. +// Before this height (inclusive), invalid blsKey sequencers are still included in the validator set +// to maintain compatibility with historical blocks. +const blsKeyCheckForkHeight = 18409547 + type validatorInfo struct { address common.Address blsPubKey blssignatures.PublicKey @@ -55,7 +60,7 @@ func (e *Executor) VerifySignature(tmPubKey []byte, messageHash []byte, blsSig [ return blssignatures.VerifySignature(sig, messageHash, blsKey) } -func (e *Executor) sequencerSetUpdates() ([][]byte, error) { +func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { seqHash, err := e.sequencerCaller.SequencerSetVerifyHash(nil) if err != nil { return nil, err @@ -98,8 +103,11 @@ func (e *Executor) sequencerSetUpdates() ([][]byte, error) { blsPK, err := decodeBlsPubKey(stakesInfo[i].BlsKey) if err != nil { e.logger.Error("failed to decode bls key", "key bytes", hexutil.Encode(stakesInfo[i].BlsKey), "error", err) - continue - // return nil, err + // Before blsKeyCheckForkHeight (inclusive), include sequencers with invalid blsKey + // to maintain compatibility with historical blocks + if height > blsKeyCheckForkHeight { + continue + } } // sequencerSet2 is the latest updated sequencer set which is considered as the next validator set for tendermint if slices.Contains(sequencerSet2, stakesInfo[i].Addr) { @@ -148,8 +156,8 @@ func (e *Executor) batchParamsUpdates(height uint64) (*tmproto.BatchParams, erro return nil, nil } -func (e *Executor) updateSequencerSet() ([][]byte, error) { - validatorUpdates, err := e.sequencerSetUpdates() +func (e *Executor) updateSequencerSet(height uint64) ([][]byte, error) { + validatorUpdates, err := e.sequencerSetUpdates(height) if err != nil { e.logger.Error("failed to get sequencer set from geth", "err", err) return nil, err From 8f7cb935c5ab7bdac476e24caa5774a3c1342e04 Mon Sep 17 00:00:00 2001 From: "fletcher.fan" Date: Mon, 15 Dec 2025 15:35:27 +0800 Subject: [PATCH 2/3] bypass cache at fork boundary for correct validator set --- node/core/sequencers.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/core/sequencers.go b/node/core/sequencers.go index 3d4bb0d65..0410864ea 100644 --- a/node/core/sequencers.go +++ b/node/core/sequencers.go @@ -65,7 +65,9 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { if err != nil { return nil, err } - if e.currentSeqHash != nil && bytes.Equal(e.currentSeqHash[:], seqHash[:]) { + // Don't use cache at fork height boundary to ensure correct blsKey validation behavior change + atForkBoundary := height == blsKeyCheckForkHeight || height == blsKeyCheckForkHeight+1 + if e.currentSeqHash != nil && bytes.Equal(e.currentSeqHash[:], seqHash[:]) && !atForkBoundary { return e.nextValidators, nil } From 47caf6ea1e8326a4534ee04e61aa9e96407743e5 Mon Sep 17 00:00:00 2001 From: "fletcher.fan" Date: Mon, 15 Dec 2025 16:23:11 +0800 Subject: [PATCH 3/3] make blsKeyCheckForkHeight configurable --- node/core/config.go | 11 ++++++----- node/core/executor.go | 42 ++++++++++++++++++++--------------------- node/core/sequencers.go | 15 +++++++-------- node/flags/flags.go | 5 ----- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/node/core/config.go b/node/core/config.go index f14d66488..cd3e80aae 100644 --- a/node/core/config.go +++ b/node/core/config.go @@ -23,8 +23,8 @@ import ( ) var ( - MainnetUpgradeBatchTime uint64 = 2000 - HoleskyUpgradeBatchTime uint64 = 350000 + MainnetUpgradeBatchTime uint64 = 0 + MainnetBlsKeyCheckForkHeight uint64 = 18409547 ) type Config struct { @@ -35,6 +35,7 @@ type Config struct { L2StakingAddress common.Address `json:"l2staking_address"` MaxL1MessageNumPerBlock uint64 `json:"max_l1_message_num_per_block"` UpgradeBatchTime uint64 `json:"upgrade_batch_time"` + BlsKeyCheckForkHeight uint64 `json:"bls_key_check_fork_height"` DevSequencer bool `json:"dev_sequencer"` Logger tmlog.Logger `json:"logger"` } @@ -157,12 +158,12 @@ func (c *Config) SetCliContext(ctx *cli.Context) error { c.DevSequencer = ctx.GlobalBool(flags.DevSequencer.Name) } - // setup batch upgrade index + // setup batch upgrade index and fork heights switch { case ctx.GlobalIsSet(flags.MainnetFlag.Name): c.UpgradeBatchTime = MainnetUpgradeBatchTime - case ctx.GlobalIsSet(flags.HoleskyFlag.Name): - c.UpgradeBatchTime = HoleskyUpgradeBatchTime + c.BlsKeyCheckForkHeight = MainnetBlsKeyCheckForkHeight + logger.Info("set UpgradeBatchTime: ", c.UpgradeBatchTime, "BlsKeyCheckForkHeight: ", c.BlsKeyCheckForkHeight) case ctx.GlobalIsSet(flags.UpgradeBatchTime.Name): c.UpgradeBatchTime = ctx.GlobalUint64(flags.UpgradeBatchTime.Name) logger.Info("set UpgradeBatchTime: ", ctx.GlobalUint64(flags.UpgradeBatchTime.Name)) diff --git a/node/core/executor.go b/node/core/executor.go index 5818adf72..0d8895658 100644 --- a/node/core/executor.go +++ b/node/core/executor.go @@ -51,9 +51,10 @@ type Executor struct { isSequencer bool devSequencer bool - UpgradeBatchTime uint64 - rollupABI *abi.ABI - batchingCache *BatchingCache + UpgradeBatchTime uint64 + blsKeyCheckForkHeight uint64 + rollupABI *abi.ABI + batchingCache *BatchingCache logger tmlog.Logger metrics *Metrics @@ -108,21 +109,22 @@ func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubK tmPubKeyBytes = tmPubKey.Bytes() } executor := &Executor{ - l2Client: l2Client, - bc: &Version1Converter{}, - govCaller: gov, - sequencerCaller: sequencer, - l2StakingCaller: l2Staking, - tmPubKey: tmPubKeyBytes, - nextL1MsgIndex: index, - maxL1MsgNumPerBlock: config.MaxL1MessageNumPerBlock, - newSyncerFunc: newSyncFunc, - devSequencer: config.DevSequencer, - rollupABI: rollupAbi, - batchingCache: NewBatchingCache(), - UpgradeBatchTime: config.UpgradeBatchTime, - logger: logger, - metrics: PrometheusMetrics("morphnode"), + l2Client: l2Client, + bc: &Version1Converter{}, + govCaller: gov, + sequencerCaller: sequencer, + l2StakingCaller: l2Staking, + tmPubKey: tmPubKeyBytes, + nextL1MsgIndex: index, + maxL1MsgNumPerBlock: config.MaxL1MessageNumPerBlock, + newSyncerFunc: newSyncFunc, + devSequencer: config.DevSequencer, + rollupABI: rollupAbi, + batchingCache: NewBatchingCache(), + UpgradeBatchTime: config.UpgradeBatchTime, + blsKeyCheckForkHeight: config.BlsKeyCheckForkHeight, + logger: logger, + metrics: PrometheusMetrics("morphnode"), } if config.DevSequencer { @@ -400,9 +402,7 @@ func (e *Executor) getParamsAndValsAtHeight(height int64) (*tmproto.BatchParams, // validate blsKey to keep consistent with sequencerSetUpdates if _, err := decodeBlsPubKey(stakesInfo[i].BlsKey); err != nil { e.logger.Error("getParamsAndValsAtHeight: failed to decode bls key", "key bytes", hexutil.Encode(stakesInfo[i].BlsKey), "error", err) - // Before blsKeyCheckForkHeight (inclusive), include sequencers with invalid blsKey - // to maintain compatibility with historical blocks - if height > blsKeyCheckForkHeight { + if e.isBlsKeyCheckFork(uint64(height)) { continue } } diff --git a/node/core/sequencers.go b/node/core/sequencers.go index 0410864ea..9632fa8d8 100644 --- a/node/core/sequencers.go +++ b/node/core/sequencers.go @@ -18,10 +18,11 @@ import ( const tmKeySize = ed25519.PubKeySize -// blsKeyCheckForkHeight is the height at which blsKey validation was enforced. -// Before this height (inclusive), invalid blsKey sequencers are still included in the validator set -// to maintain compatibility with historical blocks. -const blsKeyCheckForkHeight = 18409547 +// isBlsKeyCheckFork returns true if blsKey validation should be enforced at the given height. +// For mainnet, blsKey validation is skipped before fork height to maintain historical compatibility. +func (e *Executor) isBlsKeyCheckFork(height uint64) bool { + return e.blsKeyCheckForkHeight == 0 || height > e.blsKeyCheckForkHeight +} type validatorInfo struct { address common.Address @@ -66,7 +67,7 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { return nil, err } // Don't use cache at fork height boundary to ensure correct blsKey validation behavior change - atForkBoundary := height == blsKeyCheckForkHeight || height == blsKeyCheckForkHeight+1 + atForkBoundary := e.blsKeyCheckForkHeight > 0 && (height == e.blsKeyCheckForkHeight || height == e.blsKeyCheckForkHeight+1) if e.currentSeqHash != nil && bytes.Equal(e.currentSeqHash[:], seqHash[:]) && !atForkBoundary { return e.nextValidators, nil } @@ -105,9 +106,7 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { blsPK, err := decodeBlsPubKey(stakesInfo[i].BlsKey) if err != nil { e.logger.Error("failed to decode bls key", "key bytes", hexutil.Encode(stakesInfo[i].BlsKey), "error", err) - // Before blsKeyCheckForkHeight (inclusive), include sequencers with invalid blsKey - // to maintain compatibility with historical blocks - if height > blsKeyCheckForkHeight { + if e.isBlsKeyCheckFork(height) { continue } } diff --git a/node/flags/flags.go b/node/flags/flags.go index 7ea7c21b2..3bb690e5c 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -230,10 +230,6 @@ var ( Name: "mainnet", Usage: "Morph mainnet", } - HoleskyFlag = cli.BoolFlag{ - Name: "holesky", - Usage: "Morph Holesky", - } DerivationConfirmations = cli.Int64Flag{ Name: "derivation.confirmations", @@ -345,7 +341,6 @@ var Flags = []cli.Flag{ // batch rules UpgradeBatchTime, MainnetFlag, - HoleskyFlag, // logger LogLevel,