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 }