github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/consensus/istanbul/qbft/core/justification.go (about)

     1  package core
     2  
     3  import (
     4  	"errors"
     5  	"math/big"
     6  
     7  	"github.com/kisexp/xdchain/common"
     8  	"github.com/kisexp/xdchain/consensus/istanbul"
     9  	qbfttypes "github.com/kisexp/xdchain/consensus/istanbul/qbft/types"
    10  	"github.com/kisexp/xdchain/log"
    11  )
    12  
    13  // Returns true if the `proposal` is justified by the set `roundChangeMessages` of ROUND-CHANGE messages
    14  // and by the set `prepareMessages` of PREPARE messages.
    15  // For this we must either have:
    16  //     - a quorum of ROUND-CHANGE messages with preparedRound and preparedBlockDigest equal to nil; or
    17  //     - a ROUND-CHANGE message (1) whose preparedRound is not nil and is equal or higher than the
    18  //           preparedRound of `quorumSize` ROUND-CHANGE messages and (2) whose preparedRound and
    19  //           preparedBlockDigest match the round and block of `quorumSize` PREPARE messages.
    20  func isJustified(
    21  	proposal istanbul.Proposal,
    22  	roundChangeMessages []*qbfttypes.SignedRoundChangePayload,
    23  	prepareMessages []*qbfttypes.Prepare,
    24  	quorumSize int) error {
    25  
    26  	// Check the size of the set of ROUND-CHANGE messages
    27  	if len(roundChangeMessages) < quorumSize {
    28  		return errors.New("number of roundchange messages is less than required quorum of messages")
    29  	}
    30  
    31  	// Check the size of the set of PREPARE messages
    32  	if len(prepareMessages) != 0 && len(prepareMessages) < quorumSize {
    33  		return errors.New("number of prepared messages is less than required quorum of messages")
    34  	}
    35  
    36  	// If there are PREPARE messages, they all need to have the same round and match `proposal`
    37  	var preparedRound *big.Int
    38  	if len(prepareMessages) > 0 {
    39  		preparedRound = prepareMessages[0].Round
    40  		for _, spp := range prepareMessages {
    41  			if preparedRound.Cmp(spp.Round) != 0 || proposal.Hash() != spp.Digest {
    42  				return errors.New("prepared messages do not have same round or do not match proposal")
    43  			}
    44  		}
    45  	}
    46  
    47  	if preparedRound == nil {
    48  		return hasQuorumOfRoundChangeMessagesForNil(roundChangeMessages, quorumSize)
    49  	} else {
    50  		return hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock(roundChangeMessages, preparedRound, proposal, quorumSize)
    51  	}
    52  }
    53  
    54  // Checks whether a set of ROUND-CHANGE messages has `quorumSize` messages with nil prepared round and
    55  // prepared block.
    56  func hasQuorumOfRoundChangeMessagesForNil(roundChangeMessages []*qbfttypes.SignedRoundChangePayload, quorumSize int) error {
    57  	nilCount := 0
    58  	for _, m := range roundChangeMessages {
    59  		log.Trace("QBFT: hasQuorumOfRoundChangeMessagesForNil", "rc", m)
    60  		if (m.PreparedRound == nil || m.PreparedRound.Cmp(common.Big0) == 0) && common.EmptyHash(m.PreparedDigest) {
    61  			nilCount++
    62  			if nilCount == quorumSize {
    63  				return nil
    64  			}
    65  		}
    66  	}
    67  	return errors.New("quorum of roundchange messages with nil prepared round not found")
    68  }
    69  
    70  // Checks whether a set of ROUND-CHANGE messages has some message with `preparedRound` and `preparedBlockDigest`,
    71  // and has `quorumSize` messages with prepared round equal to nil or equal or lower than `preparedRound`.
    72  func hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock(roundChangeMessages []*qbfttypes.SignedRoundChangePayload, preparedRound *big.Int, preparedBlock istanbul.Proposal, quorumSize int) error {
    73  	lowerOrEqualRoundCount := 0
    74  	hasMatchingMessage := false
    75  	for _, m := range roundChangeMessages {
    76  		log.Trace("QBFT: hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock", "rc", m)
    77  		if m.PreparedRound == nil || m.PreparedRound.Cmp(preparedRound) <= 0 {
    78  			lowerOrEqualRoundCount++
    79  			if m.PreparedRound != nil && m.PreparedRound.Cmp(preparedRound) == 0 && m.PreparedDigest == preparedBlock.Hash() {
    80  				hasMatchingMessage = true
    81  			}
    82  			if lowerOrEqualRoundCount >= quorumSize && hasMatchingMessage {
    83  				return nil
    84  			}
    85  		}
    86  	}
    87  
    88  	return errors.New("quorum of roundchange messages with prepared round and proposal not found")
    89  }
    90  
    91  // Checks whether the round and block of a set of PREPARE messages of at least quorumSize match the
    92  // preparedRound and preparedBlockDigest of a ROUND-CHANGE qbfttypes.
    93  func hasMatchingRoundChangeAndPrepares(
    94  	roundChange *qbfttypes.RoundChange, prepareMessages []*qbfttypes.Prepare, quorumSize int) error {
    95  
    96  	if len(prepareMessages) < quorumSize {
    97  		return errors.New("number of prepare messages is less than quorum of messages")
    98  	}
    99  
   100  	for _, spp := range prepareMessages {
   101  		if spp.Digest != roundChange.PreparedDigest {
   102  			return errors.New("prepared message digest does not match roundchange prepared digest")
   103  		}
   104  		if spp.Round.Cmp(roundChange.PreparedRound) != 0 {
   105  			return errors.New("round number in prepared message does not match prepared round in roundchange")
   106  		}
   107  	}
   108  	return nil
   109  }