github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/core/roundchange.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"math/big"
    21  	"sort"
    22  	"sync"
    23  
    24  	"github.com/electroneum/electroneum-sc/common"
    25  	"github.com/electroneum/electroneum-sc/common/hexutil"
    26  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
    27  	qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types"
    28  	"github.com/electroneum/electroneum-sc/core/types"
    29  	"github.com/electroneum/electroneum-sc/rlp"
    30  )
    31  
    32  // broadcastNextRoundChange sends the ROUND CHANGE message with current round + 1
    33  func (c *core) broadcastNextRoundChange() {
    34  	cv := c.currentView()
    35  	c.broadcastRoundChange(new(big.Int).Add(cv.Round, common.Big1))
    36  }
    37  
    38  // broadcastRoundChange is called when either
    39  // - ROUND-CHANGE timeout expires (meaning either we have not received PRE-PREPARE message or we have not received a quorum of COMMIT messages)
    40  // -
    41  
    42  // It
    43  // - Creates and sign ROUND-CHANGE message
    44  // - broadcast the ROUND-CHANGE message with the given round
    45  func (c *core) broadcastRoundChange(round *big.Int) {
    46  	logger := c.currentLogger(true, nil)
    47  
    48  	// Validates new round corresponds to current view
    49  	cv := c.currentView()
    50  	if cv.Round.Cmp(round) > 0 {
    51  		logger.Error("IBFT: invalid past target round", "target", round)
    52  		return
    53  	}
    54  
    55  	// Check if we should round-change due to a bad block proposal
    56  	hasBadProposal := false
    57  	if c.current.preparedBlock != nil {
    58  		hasBadProposal = c.current.hasBadProposal(c.current.preparedBlock.Hash())
    59  	}
    60  
    61  	roundChange := qbfttypes.NewRoundChange(c.current.Sequence(), round, c.current.preparedRound, c.current.preparedBlock, hasBadProposal)
    62  
    63  	// Sign message
    64  	encodedPayload, err := roundChange.EncodePayloadForSigning()
    65  	if err != nil {
    66  		withMsg(logger, roundChange).Error("IBFT: failed to encode ROUND-CHANGE message", "err", err)
    67  		return
    68  	}
    69  	signature, err := c.backend.Sign(encodedPayload)
    70  	if err != nil {
    71  		withMsg(logger, roundChange).Error("IBFT: failed to sign ROUND-CHANGE message", "err", err)
    72  		return
    73  	}
    74  	roundChange.SetSignature(signature)
    75  
    76  	// Extend ROUND-CHANGE message with PREPARE justification
    77  	if c.QBFTPreparedPrepares != nil {
    78  		roundChange.Justification = c.QBFTPreparedPrepares
    79  		withMsg(logger, roundChange).Debug("IBFT: extended ROUND-CHANGE message with PREPARE justification", "justification", roundChange.Justification)
    80  	}
    81  
    82  	// RLP-encode message
    83  	data, err := rlp.EncodeToBytes(roundChange)
    84  	if err != nil {
    85  		withMsg(logger, roundChange).Error("IBFT: failed to encode ROUND-CHANGE message", "err", err)
    86  		return
    87  	}
    88  
    89  	withMsg(logger, roundChange).Info("IBFT: broadcast ROUND-CHANGE message", "payload", hexutil.Encode(data))
    90  
    91  	// Broadcast RLP-encoded message
    92  	if err = c.backend.Broadcast(c.valSet, roundChange.Code(), data); err != nil {
    93  		withMsg(logger, roundChange).Error("IBFT: failed to broadcast ROUND-CHANGE message", "err", err)
    94  		return
    95  	}
    96  }
    97  
    98  // handleRoundChange is called when receiving a ROUND-CHANGE message from another validator
    99  // - accumulates ROUND-CHANGE messages until reaching quorum for a given round
   100  // - when quorum of ROUND-CHANGE messages is reached then
   101  func (c *core) handleRoundChange(roundChange *qbfttypes.RoundChange) error {
   102  	logger := c.currentLogger(true, roundChange)
   103  
   104  	view := roundChange.View()
   105  	currentRound := c.currentView().Round
   106  
   107  	// number of validators we received ROUND-CHANGE from for a round higher than the current one
   108  	num := c.roundChangeSet.higherRoundMessages(currentRound)
   109  
   110  	// number of validators we received ROUND-CHANGE from for the current round
   111  	currentRoundMessages := c.roundChangeSet.getRCMessagesForGivenRound(currentRound)
   112  
   113  	logger.Info("IBFT: handle ROUND-CHANGE message", "higherRoundChanges.count", num, "currentRoundChanges.count", currentRoundMessages)
   114  
   115  	// Add ROUND-CHANGE message to message set
   116  	if view.Round.Cmp(currentRound) >= 0 {
   117  		var prepareMessages []*qbfttypes.Prepare = nil
   118  		var pr *big.Int = nil
   119  		var pb *types.Block = nil
   120  		if roundChange.PreparedRound != nil && roundChange.PreparedBlock != nil && roundChange.Justification != nil && len(roundChange.Justification) > 0 {
   121  			prepareMessages = roundChange.Justification
   122  			pr = roundChange.PreparedRound
   123  			pb = roundChange.PreparedBlock
   124  		}
   125  		err := c.roundChangeSet.Add(view.Round, roundChange, pr, pb, prepareMessages, c.QuorumSize())
   126  		if err != nil {
   127  			logger.Warn("IBFT: failed to add ROUND-CHANGE message", "err", err)
   128  			return err
   129  		}
   130  	}
   131  
   132  	// number of validators we received ROUND-CHANGE from for a round higher than the current one
   133  	num = c.roundChangeSet.higherRoundMessages(currentRound)
   134  
   135  	// number of validators we received ROUND-CHANGE from for the current round
   136  	currentRoundMessages = c.roundChangeSet.getRCMessagesForGivenRound(currentRound)
   137  
   138  	logger = logger.New("higherRoundChanges.count", num, "currentRoundChanges.count", currentRoundMessages)
   139  
   140  	if num == c.valSet.F()+1 {
   141  		// We received F+1 ROUND-CHANGE messages (this may happen before our timeout exprired)
   142  		// we start new round and broadcast ROUND-CHANGE message
   143  		newRound := c.roundChangeSet.getMinRoundChange(currentRound)
   144  
   145  		logger.Info("IBFT: received F+1 ROUND-CHANGE messages", "F", c.valSet.F())
   146  
   147  		c.startNewRound(newRound)
   148  		c.broadcastRoundChange(newRound)
   149  	} else if currentRoundMessages >= c.QuorumSize() && c.IsProposer() && c.current.preprepareSent.Cmp(currentRound) < 0 {
   150  		logger.Info("IBFT: received quorum of ROUND-CHANGE messages")
   151  
   152  		// We received quorum of ROUND-CHANGE for current round and we are proposer
   153  
   154  		// If we have received a quorum of PREPARE message
   155  		// then we propose the same block proposal again if not we
   156  		// propose the block proposal that we generated
   157  		//
   158  		// If we have received a quorum of PREPARE messages with hadBlockProposal=false,
   159  		// propose the same block again. If hadBlockProposal=true, propose the block that we generated
   160  		_, proposal := c.highestPrepared(currentRound)
   161  		if proposal == nil || c.backend.HasBadProposal(proposal.Hash()) {
   162  			proposal = c.current.pendingRequest.Proposal
   163  		}
   164  
   165  		// Prepare justification for ROUND-CHANGE messages
   166  		roundChangeMessages := c.roundChangeSet.roundChanges[currentRound.Uint64()]
   167  		rcSignedPayloads := make([]*qbfttypes.SignedRoundChangePayload, 0)
   168  		for _, m := range roundChangeMessages.Values() {
   169  			rcMsg := m.(*qbfttypes.RoundChange)
   170  			rcSignedPayloads = append(rcSignedPayloads, &rcMsg.SignedRoundChangePayload)
   171  		}
   172  
   173  		prepareMessages := c.roundChangeSet.prepareMessages[currentRound.Uint64()]
   174  		if err := isJustified(proposal, rcSignedPayloads, prepareMessages, c.QuorumSize()); err != nil {
   175  			logger.Error("IBFT: invalid ROUND-CHANGE message justification", "err", err)
   176  			return nil
   177  		}
   178  
   179  		r := &Request{
   180  			Proposal:        proposal,
   181  			RCMessages:      roundChangeMessages,
   182  			PrepareMessages: prepareMessages,
   183  		}
   184  		c.sendPreprepareMsg(r)
   185  	} else {
   186  		logger.Debug("IBFT: accepted ROUND-CHANGE messages")
   187  	}
   188  	return nil
   189  }
   190  
   191  // highestPrepared returns the highest Prepared Round and the corresponding Prepared Block
   192  func (c *core) highestPrepared(round *big.Int) (*big.Int, istanbul.Proposal) {
   193  	return c.roundChangeSet.highestPreparedRound[round.Uint64()], c.roundChangeSet.highestPreparedBlock[round.Uint64()]
   194  }
   195  
   196  // ----------------------------------------------------------------------------
   197  
   198  func newRoundChangeSet(valSet istanbul.ValidatorSet) *roundChangeSet {
   199  	return &roundChangeSet{
   200  		validatorSet:         valSet,
   201  		roundChanges:         make(map[uint64]*qbftMsgSet),
   202  		prepareMessages:      make(map[uint64][]*qbfttypes.Prepare),
   203  		highestPreparedRound: make(map[uint64]*big.Int),
   204  		highestPreparedBlock: make(map[uint64]istanbul.Proposal),
   205  		mu:                   new(sync.Mutex),
   206  	}
   207  }
   208  
   209  type roundChangeSet struct {
   210  	validatorSet         istanbul.ValidatorSet
   211  	roundChanges         map[uint64]*qbftMsgSet
   212  	prepareMessages      map[uint64][]*qbfttypes.Prepare
   213  	highestPreparedRound map[uint64]*big.Int
   214  	highestPreparedBlock map[uint64]istanbul.Proposal
   215  	mu                   *sync.Mutex
   216  }
   217  
   218  func (rcs *roundChangeSet) NewRound(r *big.Int) {
   219  	rcs.mu.Lock()
   220  	defer rcs.mu.Unlock()
   221  	round := r.Uint64()
   222  	if rcs.roundChanges[round] == nil {
   223  		rcs.roundChanges[round] = newQBFTMsgSet(rcs.validatorSet)
   224  	}
   225  	if rcs.prepareMessages[round] == nil {
   226  		rcs.prepareMessages[round] = make([]*qbfttypes.Prepare, 0)
   227  	}
   228  }
   229  
   230  // Add adds the round and message into round change set
   231  func (rcs *roundChangeSet) Add(r *big.Int, msg qbfttypes.QBFTMessage, preparedRound *big.Int, preparedBlock istanbul.Proposal, prepareMessages []*qbfttypes.Prepare, quorumSize int) error {
   232  	rcs.mu.Lock()
   233  	defer rcs.mu.Unlock()
   234  
   235  	round := r.Uint64()
   236  	if rcs.roundChanges[round] == nil {
   237  		rcs.roundChanges[round] = newQBFTMsgSet(rcs.validatorSet)
   238  	}
   239  	if err := rcs.roundChanges[round].Add(msg); err != nil {
   240  		return err
   241  	}
   242  
   243  	if preparedRound != nil && (rcs.highestPreparedRound[round] == nil || preparedRound.Cmp(rcs.highestPreparedRound[round]) > 0) {
   244  		roundChange := msg.(*qbfttypes.RoundChange)
   245  		if hasMatchingRoundChangeAndPrepares(roundChange, prepareMessages, quorumSize, roundChange.HasBadProposal) == nil {
   246  			rcs.highestPreparedRound[round] = preparedRound
   247  			rcs.highestPreparedBlock[round] = preparedBlock
   248  			rcs.prepareMessages[round] = prepareMessages
   249  		}
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  // higherRoundMessages returns the count of validators we received a ROUND-CHANGE message from
   256  // for any round greater than the given round
   257  func (rcs *roundChangeSet) higherRoundMessages(round *big.Int) int {
   258  	rcs.mu.Lock()
   259  	defer rcs.mu.Unlock()
   260  
   261  	addresses := make(map[common.Address]struct{})
   262  	for k, rms := range rcs.roundChanges {
   263  		if k > round.Uint64() {
   264  			for addr := range rms.messages {
   265  				addresses[addr] = struct{}{}
   266  			}
   267  		}
   268  	}
   269  	return len(addresses)
   270  }
   271  
   272  // getRCMessagesForGivenRound return the count ROUND-CHANGE messages
   273  // received for a given round
   274  func (rcs *roundChangeSet) getRCMessagesForGivenRound(round *big.Int) int {
   275  	rcs.mu.Lock()
   276  	defer rcs.mu.Unlock()
   277  
   278  	if rms := rcs.roundChanges[round.Uint64()]; rms != nil {
   279  		return len(rms.messages)
   280  	}
   281  	return 0
   282  }
   283  
   284  // getMinRoundChange returns the minimum round greater than the given round
   285  func (rcs *roundChangeSet) getMinRoundChange(round *big.Int) *big.Int {
   286  	rcs.mu.Lock()
   287  	defer rcs.mu.Unlock()
   288  
   289  	var keys []int
   290  	for k := range rcs.roundChanges {
   291  		if k > round.Uint64() {
   292  			keys = append(keys, int(k))
   293  		}
   294  	}
   295  	sort.Ints(keys)
   296  	if len(keys) == 0 {
   297  		return round
   298  	}
   299  	return big.NewInt(int64(keys[0]))
   300  }
   301  
   302  // ClearLowerThan deletes the messages for round earlier than the given round
   303  func (rcs *roundChangeSet) ClearLowerThan(round *big.Int) {
   304  	rcs.mu.Lock()
   305  	defer rcs.mu.Unlock()
   306  
   307  	for k, rms := range rcs.roundChanges {
   308  		if len(rms.Values()) == 0 || k < round.Uint64() {
   309  			delete(rcs.roundChanges, k)
   310  			delete(rcs.highestPreparedRound, k)
   311  			delete(rcs.highestPreparedBlock, k)
   312  			delete(rcs.prepareMessages, k)
   313  		}
   314  	}
   315  }
   316  
   317  // MaxRound returns the max round which the number of messages is equal or larger than num
   318  func (rcs *roundChangeSet) MaxRound(num int) *big.Int {
   319  	rcs.mu.Lock()
   320  	defer rcs.mu.Unlock()
   321  
   322  	var maxRound *big.Int
   323  	for k, rms := range rcs.roundChanges {
   324  		if rms.Size() < num {
   325  			continue
   326  		}
   327  		r := big.NewInt(int64(k))
   328  		if maxRound == nil || maxRound.Cmp(r) < 0 {
   329  			maxRound = r
   330  		}
   331  	}
   332  	return maxRound
   333  }