Skip to content

Commit 26d1b77

Browse files
authored
Guard emergency recovery ViewID transitions (#5107)
* fix: guard emergency recovery ViewID transitions * fix: apply recovery ViewID floor to shard 1
1 parent 6dcebd6 commit 26d1b77

15 files changed

Lines changed: 847 additions & 63 deletions

‎cmd/harmony/main.go‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -783,16 +783,41 @@ func setupConsensusAndNode(hc harmonyconfig.HarmonyConfig, nodeConfig *nodeconfi
783783
Msg("InitConsensusWithMembers failed")
784784
}
785785

786-
// Set the consensus ID to be the current block number
786+
// Offline maintenance never starts consensus signing, so it must remain
787+
// usable for preflight and rollback even before release constants are filled.
788+
// Every networked validator installs the floor before any signing service.
789+
if !hc.General.IsOffline {
790+
if err := currentConsensus.ConfigureEmergencyRecoveryViewIDFloor(); err != nil {
791+
utils.Logger().Panic().Err(err).
792+
Msg("refusing to start without a valid emergency recovery ViewID floor")
793+
}
794+
}
795+
796+
// Set the consensus ID to the checked successor of the retained head's view.
787797
viewID := currentNode.Blockchain().CurrentBlock().Header().ViewID().Uint64()
788-
currentConsensus.SetViewIDs(viewID + 1)
798+
nextViewID, err := consensus.CheckedNextViewID(viewID)
799+
if err != nil {
800+
utils.Logger().Panic().Err(err).
801+
Uint64("headViewID", viewID).
802+
Msg("refusing to start because the head ViewID cannot advance")
803+
}
804+
currentConsensus.SetViewIDs(nextViewID)
789805
utils.Logger().Info().
790-
Uint64("viewID", viewID).
806+
Uint64("headViewID", viewID).
807+
Uint64("consensusViewID", currentConsensus.GetCurBlockViewID()).
791808
Msg("Init Blockchain")
792809

793810
currentNode.Consensus.Registry().SetNodeConfig(currentNode.NodeConfig)
794-
// update consensus information based on the blockchain
795-
currentConsensus.SetMode(currentConsensus.UpdateConsensusInformation("setupConsensusAndNode"))
811+
// Update the committee first, then derive the leader from the exact recovery
812+
// view gap before StartChannel can trigger a proposal.
813+
mode := currentConsensus.UpdateConsensusInformation("setupConsensusAndNode")
814+
if !hc.General.IsOffline {
815+
if err := currentConsensus.InitializeEmergencyRecoveryLeader(); err != nil {
816+
utils.Logger().Panic().Err(err).
817+
Msg("refusing to start without a deterministic emergency recovery leader")
818+
}
819+
}
820+
currentConsensus.SetMode(mode)
796821
currentConsensus.NextBlockDue = time.Now()
797822
return currentNode
798823
}

‎consensus/checks.go‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ func (consensus *Consensus) newBlockSanityChecks(
142142

143143
// TODO: leo: move the sanity check to p2p message validation
144144
func (consensus *Consensus) onViewChangeSanityCheck(recvMsg *FBFTMessage) bool {
145+
if err := consensus.assertEmergencyRecoveryViewID(recvMsg.ViewID); err != nil {
146+
consensus.getLogger().Warn().Err(err).
147+
Uint64("MsgViewChangingID", recvMsg.ViewID).
148+
Msg("[onViewChangeSanityCheck] rejected ViewID below recovery floor")
149+
return false
150+
}
145151
// TODO: if difference is only one, new leader can still propose the same committed block to avoid another view change
146152
// TODO: new leader catchup without ignore view change message
147153

@@ -194,6 +200,12 @@ func (consensus *Consensus) onViewChangeSanityCheck(recvMsg *FBFTMessage) bool {
194200

195201
// TODO: leo: move the sanity check to p2p message validation
196202
func (consensus *Consensus) onNewViewSanityCheck(recvMsg *FBFTMessage) bool {
203+
if err := consensus.assertEmergencyRecoveryViewID(recvMsg.ViewID); err != nil {
204+
consensus.getLogger().Warn().Err(err).
205+
Uint64("MsgViewChangingID", recvMsg.ViewID).
206+
Msg("[onNewView] rejected ViewID below recovery floor")
207+
return false
208+
}
197209
if recvMsg.ViewID < consensus.getCurBlockViewID() {
198210
consensus.getLogger().Warn().
199211
Uint64("LastSuccessfulConsensusViewID", consensus.getCurBlockViewID()).
@@ -203,3 +215,21 @@ func (consensus *Consensus) onNewViewSanityCheck(recvMsg *FBFTMessage) bool {
203215
}
204216
return true
205217
}
218+
219+
func (consensus *Consensus) validateExpectedNewViewLeader(sender *bls.PublicKeyWrapper, viewID uint64) error {
220+
if sender == nil {
221+
return errors.New("NEWVIEW sender is missing")
222+
}
223+
expected := consensus.getLeaderPubKey()
224+
if consensus.current.GetViewIDFloor() > 0 {
225+
var err error
226+
expected, err = consensus.expectedLeaderForViewID(viewID)
227+
if err != nil {
228+
return err
229+
}
230+
}
231+
if expected == nil || sender.Bytes != expected.Bytes {
232+
return errors.New("NEWVIEW sender is not the selected leader for ViewID")
233+
}
234+
return nil
235+
}

‎consensus/consensus_block_proposing.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const (
2323

2424
// ProposeNewBlock proposes a new block...
2525
func (consensus *Consensus) ProposeNewBlock(now time.Time, commitSigs chan []byte) (*types.Block, error) {
26+
if err := consensus.assertEmergencyRecoveryViewID(consensus.getCurBlockViewID()); err != nil {
27+
return nil, errors.Wrap(err, "refusing to propose with unsafe ViewID")
28+
}
2629
var (
2730
currentHeader = consensus.Blockchain().CurrentHeader()
2831
nowEpoch = currentHeader.Epoch()

‎consensus/consensus_service.go‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ func (consensus *Consensus) RegisterRndChannel(rndChannel chan [548]byte) {
268268

269269
// Check viewID, caller's responsibility to hold lock when change ignoreViewIDCheck
270270
func (consensus *Consensus) checkViewID(msg *FBFTMessage) error {
271+
if err := consensus.assertEmergencyRecoveryViewID(msg.ViewID); err != nil {
272+
return err
273+
}
271274
// just ignore consensus check for the first time when node join
272275
if consensus.IgnoreViewIDCheck.IsSet() {
273276
//in syncing mode, node accepts incoming messages without viewID/leaderKey checking
@@ -494,8 +497,9 @@ func (consensus *Consensus) updateConsensusInformation(reason string) Mode {
494497
}
495498

496499
// If the leader changed and I myself become the leader
497-
if (oldLeader != nil && consensus.getLeaderPubKey() != nil &&
498-
!consensus.getLeaderPubKey().Object.IsEqual(oldLeader.Object)) && consensus.isLeader() {
500+
if consensus.current.GetViewIDFloor() == 0 &&
501+
(oldLeader != nil && consensus.getLeaderPubKey() != nil &&
502+
!consensus.getLeaderPubKey().Object.IsEqual(oldLeader.Object)) && consensus.isLeader() {
499503
go func() {
500504
consensus.GetLogger().Info().
501505
Str("myKey", myPubKeys.SerializeToHexStr()).
@@ -619,6 +623,9 @@ func (consensus *Consensus) selfCommit(payload []byte) error {
619623
if block == nil {
620624
return errGetPreparedBlock
621625
}
626+
if err := consensus.assertEmergencyRecoveryBlockViewID(block.Header().ViewID().Uint64()); err != nil {
627+
return err
628+
}
622629

623630
aggSig, mask, err := readSignatureBitmapPayload(payload, 32, consensus.decider().Participants())
624631
if err != nil {

‎consensus/consensus_v2.go‎

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,16 @@ func (consensus *Consensus) StartChannel() {
377377
func (consensus *Consensus) syncReadyChan(reason string) {
378378
consensus.getLogger().Info().Msgf("[ConsensusMainLoop] syncReadyChan %s", reason)
379379
if consensus.getBlockNum() < consensus.Blockchain().CurrentHeader().Number().Uint64()+1 {
380-
consensus.setBlockNum(consensus.Blockchain().CurrentHeader().Number().Uint64() + 1)
381-
consensus.setViewIDs(consensus.Blockchain().CurrentHeader().ViewID().Uint64() + 1)
380+
currentHeader := consensus.Blockchain().CurrentHeader()
381+
nextViewID, err := checkedNextViewID(currentHeader.ViewID().Uint64())
382+
if err != nil {
383+
consensus.getLogger().Error().Err(err).
384+
Uint64("headViewID", currentHeader.ViewID().Uint64()).
385+
Msg("[syncReadyChan] refusing to reset to an exhausted ViewID")
386+
return
387+
}
388+
consensus.setBlockNum(currentHeader.Number().Uint64() + 1)
389+
consensus.setViewIDs(nextViewID)
382390
mode := consensus.updateConsensusInformation(reason)
383391
consensus.current.SetMode(mode)
384392
consensus.getLogger().Info().Msg("[syncReadyChan] Start consensus timer")
@@ -560,6 +568,11 @@ func (consensus *Consensus) preCommitAndPropose(blk *types.Block) error {
560568
if blk == nil {
561569
return errors.New("block to pre-commit is nil")
562570
}
571+
if consensus.current.GetViewIDFloor() > 0 {
572+
if err := consensus.validateEmergencyRecoveryMessageBlockViewID(blk, consensus.getCurBlockViewID()); err != nil {
573+
return err
574+
}
575+
}
563576

564577
leaderPriKey, err := consensus.getConsensusLeaderPrivateKey()
565578
if err != nil {
@@ -579,13 +592,19 @@ func (consensus *Consensus) preCommitAndPropose(blk *types.Block) error {
579592
network.Bytes,
580593
network.FBFTMsg
581594
bareMinimumCommit := FBFTMsg.Payload
582-
consensus.fBFTLog.AddVerifiedMessage(FBFTMsg)
583595

584596
if err := consensus.verifyLastCommitSig(bareMinimumCommit, blk); err != nil {
585597
return errors.Wrap(err, "[preCommitAndPropose] failed verifying last commit sig")
586598
}
599+
consensus.fBFTLog.AddVerifiedMessage(FBFTMsg)
587600

588601
go func() {
602+
if consensus.current.GetViewIDFloor() > 0 {
603+
if err := consensus.validateEmergencyRecoveryMessageBlockViewID(blk, FBFTMsg.ViewID); err != nil {
604+
consensus.GetLogger().Error().Err(err).Msg("[preCommitAndPropose] unsafe recovery ViewID")
605+
return
606+
}
607+
}
589608
blk.SetCurrentCommitSig(bareMinimumCommit)
590609

591610
// Send committed message to validators since 2/3 commit is already collected
@@ -675,6 +694,13 @@ func (consensus *Consensus) tryCatchup() error {
675694
if blk == nil {
676695
return nil
677696
}
697+
if msg == nil {
698+
return errors.New("[TryCatchup] committed message is missing")
699+
}
700+
if err := consensus.validateEmergencyRecoveryMessageBlockViewID(blk, msg.ViewID); err != nil {
701+
consensus.getLogger().Error().Err(err).Msg("[TryCatchup] unsafe recovery ViewID")
702+
return err
703+
}
678704
blk.SetCurrentCommitSig(msg.Payload)
679705

680706
if err := consensus.verifyBlock(blk); err != nil {
@@ -700,6 +726,12 @@ func (consensus *Consensus) tryCatchup() error {
700726
}
701727

702728
func (consensus *Consensus) commitBlock(blk *types.Block, committedMsg *FBFTMessage) error {
729+
if committedMsg == nil {
730+
return errors.New("committed message is missing")
731+
}
732+
if err := consensus.validateEmergencyRecoveryMessageBlockViewID(blk, committedMsg.ViewID); err != nil {
733+
return err
734+
}
703735
if consensus.Blockchain().CurrentBlock().NumberU64() < blk.NumberU64() {
704736
_, err := consensus.Blockchain().InsertChain([]*types.Block{blk}, !consensus.fBFTLog.IsBlockVerified(blk.Hash()))
705737
if err != nil && !errors.Is(err, core.ErrKnownBlock) {
@@ -715,7 +747,9 @@ func (consensus *Consensus) commitBlock(blk *types.Block, committedMsg *FBFTMess
715747

716748
consensus.FinishFinalityCount()
717749
consensus.postConsensusProcessing(blk)
718-
consensus.setupForNewConsensus(blk, committedMsg)
750+
if err := consensus.setupForNewConsensus(blk, committedMsg); err != nil {
751+
return err
752+
}
719753
consensus.getLogger().Info().Uint64("blockNum", blk.NumberU64()).
720754
Str("hash", blk.Header().Hash().Hex()).
721755
Msg("Added New Block to Blockchain!!!")
@@ -840,9 +874,13 @@ func (consensus *Consensus) rotateLeader(epoch *big.Int, defaultKey *bls.PublicK
840874
}
841875

842876
// SetupForNewConsensus sets the state for new consensus
843-
func (consensus *Consensus) setupForNewConsensus(blk *types.Block, committedMsg *FBFTMessage) {
877+
func (consensus *Consensus) setupForNewConsensus(blk *types.Block, committedMsg *FBFTMessage) error {
878+
nextViewID, err := checkedNextViewID(committedMsg.ViewID)
879+
if err != nil {
880+
return errors.Wrap(err, "cannot advance consensus ViewID")
881+
}
844882
consensus.setBlockNum(blk.NumberU64() + 1)
845-
consensus.setCurBlockViewID(committedMsg.ViewID + 1)
883+
consensus.setCurBlockViewID(nextViewID)
846884
var epoch *big.Int
847885
if blk.IsLastBlockInEpoch() {
848886
epoch = new(big.Int).Add(blk.Epoch(), common.Big1)
@@ -897,6 +935,7 @@ func (consensus *Consensus) setupForNewConsensus(blk *types.Block, committedMsg
897935
consensus.fBFTLog.PruneCacheBeforeBlock(blk.NumberU64())
898936
consensus.resetState()
899937
consensus.sendLastSignPower()
938+
return nil
900939
}
901940

902941
func (consensus *Consensus) postCatchup(initBN uint64) {

‎consensus/construct.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ func (pm *State) populateMessageFieldsAndSender(
5858
func (consensus *Consensus) construct(
5959
p msg_pb.MessageType, payloadForSign []byte, priKeys []*bls.PrivateKeyWrapper,
6060
) (*NetworkMessage, error) {
61+
if err := consensus.assertEmergencyRecoveryViewID(consensus.getCurBlockViewID()); err != nil {
62+
return nil, err
63+
}
64+
switch p {
65+
case msg_pb.MessageType_ANNOUNCE, msg_pb.MessageType_PREPARE, msg_pb.MessageType_PREPARED:
66+
if err := consensus.validateCurrentConsensusBlockViewID(); err != nil {
67+
return nil, err
68+
}
69+
}
6170
if len(priKeys) == 0 {
6271
return nil, errors.New("no elected bls keys provided")
6372
}

‎consensus/leader.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import (
1818

1919
// announce fires leader
2020
func (consensus *Consensus) announce(block *types.Block) {
21+
if err := consensus.assertEmergencyRecoveryBlockViewID(block.Header().ViewID().Uint64()); err != nil {
22+
consensus.getLogger().Error().Err(err).Msg("[Announce] unsafe recovery ViewID")
23+
return
24+
}
2125
blockHash := block.Hash()
2226

2327
// prepare message and broadcast to validators
@@ -269,6 +273,12 @@ func (consensus *Consensus) onCommit(recvMsg *FBFTMessage) {
269273
Msg("[OnCommit] Failed finding a matching block for committed message")
270274
return
271275
}
276+
if consensus.current.GetViewIDFloor() > 0 {
277+
if err := consensus.validateEmergencyRecoveryMessageBlockViewID(blockObj, recvMsg.ViewID); err != nil {
278+
consensus.getLogger().Warn().Err(err).Msg("[OnCommit] unsafe recovery ViewID")
279+
return
280+
}
281+
}
272282
commitPayload := signature.ConstructCommitPayload(consensus.Blockchain().Config(),
273283
blockObj.Epoch(), blockObj.Hash(), blockObj.NumberU64(), blockObj.Header().ViewID().Uint64())
274284
logger = logger.With().

0 commit comments

Comments
 (0)