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