github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/types/height_vote_set.go (about) 1 package types 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "sync" 8 9 "github.com/evdatsion/aphelion-dpos-bft/p2p" 10 "github.com/evdatsion/aphelion-dpos-bft/types" 11 ) 12 13 type RoundVoteSet struct { 14 Prevotes *types.VoteSet 15 Precommits *types.VoteSet 16 } 17 18 var ( 19 GotVoteFromUnwantedRoundError = errors.New("Peer has sent a vote that does not match our round for more than one round") 20 ) 21 22 /* 23 Keeps track of all VoteSets from round 0 to round 'round'. 24 25 Also keeps track of up to one RoundVoteSet greater than 26 'round' from each peer, to facilitate catchup syncing of commits. 27 28 A commit is +2/3 precommits for a block at a round, 29 but which round is not known in advance, so when a peer 30 provides a precommit for a round greater than mtx.round, 31 we create a new entry in roundVoteSets but also remember the 32 peer to prevent abuse. 33 We let each peer provide us with up to 2 unexpected "catchup" rounds. 34 One for their LastCommit round, and another for the official commit round. 35 */ 36 type HeightVoteSet struct { 37 chainID string 38 height int64 39 valSet *types.ValidatorSet 40 41 mtx sync.Mutex 42 round int // max tracked round 43 roundVoteSets map[int]RoundVoteSet // keys: [0...round] 44 peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds 45 } 46 47 func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { 48 hvs := &HeightVoteSet{ 49 chainID: chainID, 50 } 51 hvs.Reset(height, valSet) 52 return hvs 53 } 54 55 func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { 56 hvs.mtx.Lock() 57 defer hvs.mtx.Unlock() 58 59 hvs.height = height 60 hvs.valSet = valSet 61 hvs.roundVoteSets = make(map[int]RoundVoteSet) 62 hvs.peerCatchupRounds = make(map[p2p.ID][]int) 63 64 hvs.addRound(0) 65 hvs.round = 0 66 } 67 68 func (hvs *HeightVoteSet) Height() int64 { 69 hvs.mtx.Lock() 70 defer hvs.mtx.Unlock() 71 return hvs.height 72 } 73 74 func (hvs *HeightVoteSet) Round() int { 75 hvs.mtx.Lock() 76 defer hvs.mtx.Unlock() 77 return hvs.round 78 } 79 80 // Create more RoundVoteSets up to round. 81 func (hvs *HeightVoteSet) SetRound(round int) { 82 hvs.mtx.Lock() 83 defer hvs.mtx.Unlock() 84 if hvs.round != 0 && (round < hvs.round+1) { 85 panic("SetRound() must increment hvs.round") 86 } 87 for r := hvs.round + 1; r <= round; r++ { 88 if _, ok := hvs.roundVoteSets[r]; ok { 89 continue // Already exists because peerCatchupRounds. 90 } 91 hvs.addRound(r) 92 } 93 hvs.round = round 94 } 95 96 func (hvs *HeightVoteSet) addRound(round int) { 97 if _, ok := hvs.roundVoteSets[round]; ok { 98 panic("addRound() for an existing round") 99 } 100 // log.Debug("addRound(round)", "round", round) 101 prevotes := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrevoteType, hvs.valSet) 102 precommits := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrecommitType, hvs.valSet) 103 hvs.roundVoteSets[round] = RoundVoteSet{ 104 Prevotes: prevotes, 105 Precommits: precommits, 106 } 107 } 108 109 // Duplicate votes return added=false, err=nil. 110 // By convention, peerID is "" if origin is self. 111 func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { 112 hvs.mtx.Lock() 113 defer hvs.mtx.Unlock() 114 if !types.IsVoteTypeValid(vote.Type) { 115 return 116 } 117 voteSet := hvs.getVoteSet(vote.Round, vote.Type) 118 if voteSet == nil { 119 if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 { 120 hvs.addRound(vote.Round) 121 voteSet = hvs.getVoteSet(vote.Round, vote.Type) 122 hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round) 123 } else { 124 // punish peer 125 err = GotVoteFromUnwantedRoundError 126 return 127 } 128 } 129 added, err = voteSet.AddVote(vote) 130 return 131 } 132 133 func (hvs *HeightVoteSet) Prevotes(round int) *types.VoteSet { 134 hvs.mtx.Lock() 135 defer hvs.mtx.Unlock() 136 return hvs.getVoteSet(round, types.PrevoteType) 137 } 138 139 func (hvs *HeightVoteSet) Precommits(round int) *types.VoteSet { 140 hvs.mtx.Lock() 141 defer hvs.mtx.Unlock() 142 return hvs.getVoteSet(round, types.PrecommitType) 143 } 144 145 // Last round and blockID that has +2/3 prevotes for a particular block or nil. 146 // Returns -1 if no such round exists. 147 func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID types.BlockID) { 148 hvs.mtx.Lock() 149 defer hvs.mtx.Unlock() 150 for r := hvs.round; r >= 0; r-- { 151 rvs := hvs.getVoteSet(r, types.PrevoteType) 152 polBlockID, ok := rvs.TwoThirdsMajority() 153 if ok { 154 return r, polBlockID 155 } 156 } 157 return -1, types.BlockID{} 158 } 159 160 func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *types.VoteSet { 161 rvs, ok := hvs.roundVoteSets[round] 162 if !ok { 163 return nil 164 } 165 switch type_ { 166 case types.PrevoteType: 167 return rvs.Prevotes 168 case types.PrecommitType: 169 return rvs.Precommits 170 default: 171 panic(fmt.Sprintf("Unexpected vote type %X", type_)) 172 } 173 } 174 175 // If a peer claims that it has 2/3 majority for given blockKey, call this. 176 // NOTE: if there are too many peers, or too much peer churn, 177 // this can cause memory issues. 178 // TODO: implement ability to remove peers too 179 func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2p.ID, blockID types.BlockID) error { 180 hvs.mtx.Lock() 181 defer hvs.mtx.Unlock() 182 if !types.IsVoteTypeValid(type_) { 183 return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_) 184 } 185 voteSet := hvs.getVoteSet(round, type_) 186 if voteSet == nil { 187 return nil // something we don't know about yet 188 } 189 return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID) 190 } 191 192 //--------------------------------------------------------- 193 // string and json 194 195 func (hvs *HeightVoteSet) String() string { 196 return hvs.StringIndented("") 197 } 198 199 func (hvs *HeightVoteSet) StringIndented(indent string) string { 200 hvs.mtx.Lock() 201 defer hvs.mtx.Unlock() 202 vsStrings := make([]string, 0, (len(hvs.roundVoteSets)+1)*2) 203 // rounds 0 ~ hvs.round inclusive 204 for round := 0; round <= hvs.round; round++ { 205 voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() 206 vsStrings = append(vsStrings, voteSetString) 207 voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() 208 vsStrings = append(vsStrings, voteSetString) 209 } 210 // all other peer catchup rounds 211 for round, roundVoteSet := range hvs.roundVoteSets { 212 if round <= hvs.round { 213 continue 214 } 215 voteSetString := roundVoteSet.Prevotes.StringShort() 216 vsStrings = append(vsStrings, voteSetString) 217 voteSetString = roundVoteSet.Precommits.StringShort() 218 vsStrings = append(vsStrings, voteSetString) 219 } 220 return fmt.Sprintf(`HeightVoteSet{H:%v R:0~%v 221 %s %v 222 %s}`, 223 hvs.height, hvs.round, 224 indent, strings.Join(vsStrings, "\n"+indent+" "), 225 indent) 226 } 227 228 func (hvs *HeightVoteSet) MarshalJSON() ([]byte, error) { 229 hvs.mtx.Lock() 230 defer hvs.mtx.Unlock() 231 232 allVotes := hvs.toAllRoundVotes() 233 return cdc.MarshalJSON(allVotes) 234 } 235 236 func (hvs *HeightVoteSet) toAllRoundVotes() []roundVotes { 237 totalRounds := hvs.round + 1 238 allVotes := make([]roundVotes, totalRounds) 239 // rounds 0 ~ hvs.round inclusive 240 for round := 0; round < totalRounds; round++ { 241 allVotes[round] = roundVotes{ 242 Round: round, 243 Prevotes: hvs.roundVoteSets[round].Prevotes.VoteStrings(), 244 PrevotesBitArray: hvs.roundVoteSets[round].Prevotes.BitArrayString(), 245 Precommits: hvs.roundVoteSets[round].Precommits.VoteStrings(), 246 PrecommitsBitArray: hvs.roundVoteSets[round].Precommits.BitArrayString(), 247 } 248 } 249 // TODO: all other peer catchup rounds 250 return allVotes 251 } 252 253 type roundVotes struct { 254 Round int `json:"round"` 255 Prevotes []string `json:"prevotes"` 256 PrevotesBitArray string `json:"prevotes_bit_array"` 257 Precommits []string `json:"precommits"` 258 PrecommitsBitArray string `json:"precommits_bit_array"` 259 }