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