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

     1  package core
     2  
     3  import (
     4  	"errors"
     5  	"math/big"
     6  
     7  	"github.com/electroneum/electroneum-sc/common"
     8  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
     9  	qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types"
    10  	"github.com/electroneum/electroneum-sc/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  	// Check the size of the set of ROUND-CHANGE messages
    26  	if len(roundChangeMessages) < quorumSize {
    27  		return errors.New("number of roundchange messages is less than required quorum of messages")
    28  	}
    29  
    30  	// Check the size of the set of PREPARE messages
    31  	if len(prepareMessages) != 0 && len(prepareMessages) < quorumSize {
    32  		return errors.New("number of prepared messages is less than required quorum of messages")
    33  	}
    34  
    35  	// Count round change messages with "bad proposal" reason
    36  	var hasBadProposalCount uint = 0
    37  	for _, rcm := range roundChangeMessages {
    38  		if rcm.HasBadProposal {
    39  			hasBadProposalCount++
    40  		}
    41  	}
    42  
    43  	// Set hasBadProposal if reached quorum
    44  	hasBadProposal := hasBadProposalCount >= uint(quorumSize)
    45  
    46  	// If there are PREPARE messages, they all need to have the same round and match `proposal`
    47  	var preparedRound *big.Int
    48  	if len(prepareMessages) > 0 {
    49  		preparedRound = prepareMessages[0].Round
    50  		for _, spp := range prepareMessages {
    51  			if preparedRound.Cmp(spp.Round) != 0 || (proposal.Hash() != spp.Digest && !hasBadProposal) {
    52  				return errors.New("prepared messages do not have same round or do not match proposal")
    53  			}
    54  		}
    55  	}
    56  
    57  	if preparedRound == nil {
    58  		return hasQuorumOfRoundChangeMessagesForNil(roundChangeMessages, quorumSize)
    59  	} else {
    60  		return hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock(roundChangeMessages, preparedRound, proposal, quorumSize, hasBadProposal)
    61  	}
    62  }
    63  
    64  // Checks whether a set of ROUND-CHANGE messages has `quorumSize` messages with nil prepared round and
    65  // prepared block.
    66  func hasQuorumOfRoundChangeMessagesForNil(roundChangeMessages []*qbfttypes.SignedRoundChangePayload, quorumSize int) error {
    67  	nilCount := 0
    68  	for _, m := range roundChangeMessages {
    69  		log.Trace("IBFT: hasQuorumOfRoundChangeMessagesForNil", "rc", m)
    70  		if (m.PreparedRound == nil || m.PreparedRound.Cmp(common.Big0) == 0) && common.EmptyHash(m.PreparedDigest) {
    71  			nilCount++
    72  			if nilCount == quorumSize {
    73  				return nil
    74  			}
    75  		}
    76  	}
    77  	return errors.New("quorum of roundchange messages with nil prepared round not found")
    78  }
    79  
    80  // Checks whether a set of ROUND-CHANGE messages has some message with `preparedRound` and `preparedBlockDigest`,
    81  // and has `quorumSize` messages with prepared round equal to nil or equal or lower than `preparedRound`.
    82  func hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock(roundChangeMessages []*qbfttypes.SignedRoundChangePayload, preparedRound *big.Int, preparedBlock istanbul.Proposal, quorumSize int, hasQuorumOfBadProposal bool) error {
    83  	lowerOrEqualRoundCount := 0
    84  	hasMatchingMessage := false
    85  	for _, m := range roundChangeMessages {
    86  		log.Trace("IBFT: hasQuorumOfRoundChangeMessagesForPreparedRoundAndBlock", "rc", m)
    87  		if m.PreparedRound == nil || m.PreparedRound.Cmp(preparedRound) <= 0 {
    88  			lowerOrEqualRoundCount++
    89  			if m.PreparedRound != nil && m.PreparedRound.Cmp(preparedRound) == 0 && (m.PreparedDigest == preparedBlock.Hash() || (m.PreparedDigest != preparedBlock.Hash() && hasQuorumOfBadProposal)) {
    90  				hasMatchingMessage = true
    91  			}
    92  			if lowerOrEqualRoundCount >= quorumSize && hasMatchingMessage {
    93  				return nil
    94  			}
    95  		}
    96  	}
    97  
    98  	return errors.New("quorum of roundchange messages with prepared round and proposal not found")
    99  }
   100  
   101  // Checks whether the round and block of a set of PREPARE messages of at least quorumSize match the
   102  // preparedRound and preparedBlockDigest of a ROUND-CHANGE qbfttypes.
   103  func hasMatchingRoundChangeAndPrepares(
   104  	roundChange *qbfttypes.RoundChange, prepareMessages []*qbfttypes.Prepare, quorumSize int, hasBadProposal bool) error {
   105  	if len(prepareMessages) < quorumSize {
   106  		return errors.New("number of prepare messages is less than quorum of messages")
   107  	}
   108  
   109  	for _, spp := range prepareMessages {
   110  		if spp.Digest != roundChange.PreparedDigest && !hasBadProposal {
   111  			return errors.New("prepared message digest does not match roundchange prepared digest")
   112  		}
   113  		if spp.Round.Cmp(roundChange.PreparedRound) != 0 {
   114  			return errors.New("round number in prepared message does not match prepared round in roundchange")
   115  		}
   116  	}
   117  	return nil
   118  }