From a23d2a7fcaf223e48dfb2742caa33a0eff447fb8 Mon Sep 17 00:00:00 2001 From: panos Date: Mon, 8 Jun 2026 16:23:09 +0800 Subject: [PATCH 1/2] fix(node): restore mainnet validator hash compatibility Preserve the height-aware invalid BLS key handling from the Emerald fork so nodes replaying mainnet history compute the expected validator set hash around height 18409547. Constraint: Keep behavior gated by the existing --mainnet flag to match the original mainnet-only compatibility path. Confidence: high Scope-risk: narrow --- node/core/config.go | 8 ++++++++ node/core/executor.go | 29 ++++++++++++++++++----------- node/core/sequencers.go | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/node/core/config.go b/node/core/config.go index dbd6e19ca..8ce2f58c7 100644 --- a/node/core/config.go +++ b/node/core/config.go @@ -23,6 +23,8 @@ import ( ) var ( + MainnetBlsKeyCheckForkHeight uint64 = 18409547 + // L1 Mainnet Contract Addresses MainnetRollupContractAddress = common.HexToAddress("0x759894ced0e6af42c26668076ffa84d02e3cef60") ) @@ -33,6 +35,7 @@ type Config struct { SequencerAddress common.Address `json:"sequencer_address"` L2StakingAddress common.Address `json:"l2staking_address"` MaxL1MessageNumPerBlock uint64 `json:"max_l1_message_num_per_block"` + BlsKeyCheckForkHeight uint64 `json:"bls_key_check_fork_height"` DevSequencer bool `json:"dev_sequencer"` Logger tmlog.Logger `json:"logger"` } @@ -146,5 +149,10 @@ func (c *Config) SetCliContext(ctx *cli.Context) error { c.DevSequencer = ctx.GlobalBool(flags.DevSequencer.Name) } + if ctx.GlobalIsSet(flags.MainnetFlag.Name) { + c.BlsKeyCheckForkHeight = MainnetBlsKeyCheckForkHeight + logger.Info("mainnet historical validator compatibility enabled", "BlsKeyCheckForkHeight", c.BlsKeyCheckForkHeight) + } + return nil } diff --git a/node/core/executor.go b/node/core/executor.go index fa8bf0a88..086703e1b 100644 --- a/node/core/executor.go +++ b/node/core/executor.go @@ -45,6 +45,8 @@ type Executor struct { isSequencer bool devSequencer bool + blsKeyCheckForkHeight uint64 + logger tmlog.Logger metrics *Metrics } @@ -90,17 +92,18 @@ func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubK tmPubKeyBytes = tmPubKey.Bytes() } executor := &Executor{ - l2Client: l2Client, - bc: &Version1Converter{}, - sequencerCaller: sequencer, - l2StakingCaller: l2Staking, - tmPubKey: tmPubKeyBytes, - nextL1MsgIndex: index, - maxL1MsgNumPerBlock: config.MaxL1MessageNumPerBlock, - newSyncerFunc: newSyncFunc, - devSequencer: config.DevSequencer, - logger: logger, - metrics: PrometheusMetrics("morphnode"), + l2Client: l2Client, + bc: &Version1Converter{}, + sequencerCaller: sequencer, + l2StakingCaller: l2Staking, + tmPubKey: tmPubKeyBytes, + nextL1MsgIndex: index, + maxL1MsgNumPerBlock: config.MaxL1MessageNumPerBlock, + newSyncerFunc: newSyncFunc, + devSequencer: config.DevSequencer, + blsKeyCheckForkHeight: config.BlsKeyCheckForkHeight, + logger: logger, + metrics: PrometheusMetrics("morphnode"), } if config.DevSequencer { @@ -356,6 +359,10 @@ func (e *Executor) getValidatorsAtHeight(height int64) ([][]byte, error) { } newValidators := make([][]byte, 0, len(addrs)) for i := range stakesInfo { + if !e.shouldKeepSequencerAtHeight(uint64(height), stakesInfo[i].BlsKey) { + e.logger.Error("getValidatorsAtHeight: skip sequencer with invalid bls key", "height", height, "addr", stakesInfo[i].Addr) + continue + } newValidators = append(newValidators, stakesInfo[i].TmKey[:]) } return newValidators, nil diff --git a/node/core/sequencers.go b/node/core/sequencers.go index e56b522b3..c4be652be 100644 --- a/node/core/sequencers.go +++ b/node/core/sequencers.go @@ -5,6 +5,7 @@ import ( "slices" "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/crypto/bls12381" "github.com/tendermint/tendermint/crypto/ed25519" ) @@ -15,7 +16,7 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { if err != nil { return nil, err } - if e.currentSeqHash != nil && bytes.Equal(e.currentSeqHash[:], seqHash[:]) { + if e.shouldReuseSequencerCache(height, seqHash) { return e.nextValidators, nil } @@ -49,6 +50,10 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { seqTmKeySet := make(map[[tmKeySize]byte]struct{}, len(stakesInfo)) nextValidators := make([][]byte, 0, len(sequencerSet2)) for i := range stakesInfo { + if !e.shouldKeepSequencerAtHeight(height, stakesInfo[i].BlsKey) { + e.logger.Error("sequencerSetUpdates: skip sequencer with invalid bls key", "height", height, "addr", stakesInfo[i].Addr) + 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) { nextValidators = append(nextValidators, stakesInfo[i].TmKey[:]) @@ -63,6 +68,31 @@ func (e *Executor) sequencerSetUpdates(height uint64) ([][]byte, error) { return nextValidators, nil } +func (e *Executor) shouldReuseSequencerCache(height uint64, seqHash [32]byte) bool { + if e.currentSeqHash == nil || !bytes.Equal(e.currentSeqHash[:], seqHash[:]) { + return false + } + + return !e.isBlsKeyCheckForkBoundary(height) +} + +func (e *Executor) isBlsKeyCheckForkBoundary(height uint64) bool { + return e.blsKeyCheckForkHeight > 0 && + (height == e.blsKeyCheckForkHeight || height == e.blsKeyCheckForkHeight+1) +} + +func (e *Executor) shouldKeepSequencerAtHeight(height uint64, blsKey []byte) bool { + if decodeBlsPubKey(blsKey) == nil { + return true + } + + return !e.shouldValidateBlsKeyAtHeight(height) +} + +func (e *Executor) shouldValidateBlsKeyAtHeight(height uint64) bool { + return e.blsKeyCheckForkHeight == 0 || height > e.blsKeyCheckForkHeight +} + func (e *Executor) updateSequencerSet(height uint64) ([][]byte, error) { validatorUpdates, err := e.sequencerSetUpdates(height) if err != nil { @@ -94,3 +124,8 @@ func (e *Executor) updateSequencerSet(height uint64) ([][]byte, error) { e.isSequencer = isSequencer return validatorUpdates, nil } + +func decodeBlsPubKey(in []byte) error { + _, err := bls12381.NewG2().DecodePoint(in) + return err +} From b19c5ac14ce36fa04206f23589e6e437a9b4d2cc Mon Sep 17 00:00:00 2001 From: panos Date: Mon, 8 Jun 2026 16:34:14 +0800 Subject: [PATCH 2/2] refactor(node): simplify validator compatibility checks Keep the mainnet historical validator hash fix focused by collapsing fork-boundary and BLS-key checks into the two call-site predicates that need them. Constraint: Preserve the existing --mainnet-gated compatibility behavior Confidence: high Scope-risk: narrow --- node/core/sequencers.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/node/core/sequencers.go b/node/core/sequencers.go index c4be652be..4c43a8cf6 100644 --- a/node/core/sequencers.go +++ b/node/core/sequencers.go @@ -73,24 +73,19 @@ func (e *Executor) shouldReuseSequencerCache(height uint64, seqHash [32]byte) bo return false } - return !e.isBlsKeyCheckForkBoundary(height) -} - -func (e *Executor) isBlsKeyCheckForkBoundary(height uint64) bool { - return e.blsKeyCheckForkHeight > 0 && - (height == e.blsKeyCheckForkHeight || height == e.blsKeyCheckForkHeight+1) + if e.blsKeyCheckForkHeight > 0 && + (height == e.blsKeyCheckForkHeight || height == e.blsKeyCheckForkHeight+1) { + return false + } + return true } func (e *Executor) shouldKeepSequencerAtHeight(height uint64, blsKey []byte) bool { - if decodeBlsPubKey(blsKey) == nil { + if isValidBlsKey(blsKey) { return true } - return !e.shouldValidateBlsKeyAtHeight(height) -} - -func (e *Executor) shouldValidateBlsKeyAtHeight(height uint64) bool { - return e.blsKeyCheckForkHeight == 0 || height > e.blsKeyCheckForkHeight + return e.blsKeyCheckForkHeight > 0 && height <= e.blsKeyCheckForkHeight } func (e *Executor) updateSequencerSet(height uint64) ([][]byte, error) { @@ -125,7 +120,7 @@ func (e *Executor) updateSequencerSet(height uint64) ([][]byte, error) { return validatorUpdates, nil } -func decodeBlsPubKey(in []byte) error { +func isValidBlsKey(in []byte) bool { _, err := bls12381.NewG2().DecodePoint(in) - return err + return err == nil }