github.com/Finschia/ostracon@v1.1.5/types/vote_set.go (about) 1 package types 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 9 10 "github.com/Finschia/ostracon/crypto" 11 "github.com/Finschia/ostracon/libs/bits" 12 tmjson "github.com/Finschia/ostracon/libs/json" 13 tmsync "github.com/Finschia/ostracon/libs/sync" 14 ) 15 16 const ( 17 // MaxVotesCount is the maximum number of votes in a set. Used in ValidateBasic funcs for 18 // protection against DOS attacks. Note this implies a corresponding equal limit to 19 // the number of validators. 20 MaxVotesCount = 10000 21 ) 22 23 // UNSTABLE 24 // XXX: duplicate of p2p.ID to avoid dependence between packages. 25 // Perhaps we can have a minimal types package containing this (and other things?) 26 // that both `types` and `p2p` import ? 27 type P2PID string 28 29 /* 30 VoteSet helps collect signatures from validators at each height+round for a 31 predefined vote type. 32 33 We need VoteSet to be able to keep track of conflicting votes when validators 34 double-sign. Yet, we can't keep track of *all* the votes seen, as that could 35 be a DoS attack vector. 36 37 There are two storage areas for votes. 38 1. voteSet.votes 39 2. voteSet.votesByBlock 40 41 `.votes` is the "canonical" list of votes. It always has at least one vote, 42 if a vote from a validator had been seen at all. Usually it keeps track of 43 the first vote seen, but when a 2/3 majority is found, votes for that get 44 priority and are copied over from `.votesByBlock`. 45 46 `.votesByBlock` keeps track of a list of votes for a particular block. There 47 are two ways a &blockVotes{} gets created in `.votesByBlock`. 48 1. the first vote seen by a validator was for the particular block. 49 2. a peer claims to have seen 2/3 majority for the particular block. 50 51 Since the first vote from a validator will always get added in `.votesByBlock` 52 , all votes in `.votes` will have a corresponding entry in `.votesByBlock`. 53 54 When a &blockVotes{} in `.votesByBlock` reaches a 2/3 majority quorum, its 55 votes are copied into `.votes`. 56 57 All this is memory bounded because conflicting votes only get added if a peer 58 told us to track that block, each peer only gets to tell us 1 such block, and, 59 there's only a limited number of peers. 60 61 NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64. 62 */ 63 type VoteSet struct { 64 chainID string 65 height int64 66 round int32 67 signedMsgType tmproto.SignedMsgType 68 valSet *ValidatorSet 69 70 mtx tmsync.Mutex 71 votesBitArray *bits.BitArray 72 votes []*Vote // Primary votes to share 73 sum int64 // Sum of voting power for seen votes, discounting conflicts 74 maj23 *BlockID // First 2/3 majority seen 75 votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes 76 peerMaj23s map[P2PID]BlockID // Maj23 for each peer 77 } 78 79 // Constructs a new VoteSet struct used to accumulate votes for given height/round. 80 func NewVoteSet(chainID string, height int64, round int32, 81 signedMsgType tmproto.SignedMsgType, valSet *ValidatorSet) *VoteSet { 82 if height == 0 { 83 panic("Cannot make VoteSet for height == 0, doesn't make sense.") 84 } 85 return &VoteSet{ 86 chainID: chainID, 87 height: height, 88 round: round, 89 signedMsgType: signedMsgType, 90 valSet: valSet, 91 votesBitArray: bits.NewBitArray(valSet.Size()), 92 votes: make([]*Vote, valSet.Size()), 93 sum: 0, 94 maj23: nil, 95 votesByBlock: make(map[string]*blockVotes, valSet.Size()), 96 peerMaj23s: make(map[P2PID]BlockID), 97 } 98 } 99 100 // used only test 101 func (voteSet *VoteSet) GetSum() int64 { 102 if voteSet == nil { 103 return 0 104 } 105 voteSet.mtx.Lock() 106 defer voteSet.mtx.Unlock() 107 return voteSet.sum 108 } 109 110 func (voteSet *VoteSet) ChainID() string { 111 return voteSet.chainID 112 } 113 114 // Implements VoteSetReader. 115 func (voteSet *VoteSet) GetHeight() int64 { 116 if voteSet == nil { 117 return 0 118 } 119 return voteSet.height 120 } 121 122 // Implements VoteSetReader. 123 func (voteSet *VoteSet) GetRound() int32 { 124 if voteSet == nil { 125 return -1 126 } 127 return voteSet.round 128 } 129 130 // Implements VoteSetReader. 131 func (voteSet *VoteSet) Type() byte { 132 if voteSet == nil { 133 return 0x00 134 } 135 return byte(voteSet.signedMsgType) 136 } 137 138 // Implements VoteSetReader. 139 func (voteSet *VoteSet) Size() int { 140 if voteSet == nil { 141 return 0 142 } 143 return voteSet.valSet.Size() 144 } 145 146 // Returns added=true if vote is valid and new. 147 // Otherwise returns err=ErrVote[ 148 // 149 // UnexpectedStep | InvalidIndex | InvalidAddress | 150 // InvalidSignature | InvalidBlockHash | ConflictingVotes ] 151 // 152 // Duplicate votes return added=false, err=nil. 153 // Conflicting votes return added=*, err=ErrVoteConflictingVotes. 154 // NOTE: vote should not be mutated after adding. 155 // NOTE: VoteSet must not be nil 156 // NOTE: Vote must not be nil 157 func (voteSet *VoteSet) AddVote(vote *Vote) (added bool, err error) { 158 if voteSet == nil { 159 panic("AddVote() on nil VoteSet") 160 } 161 voteSet.mtx.Lock() 162 defer voteSet.mtx.Unlock() 163 164 return voteSet.addVote(vote, vote.Verify) 165 } 166 167 // NOTE: Validates as much as possible before attempting to verify the signature. 168 func (voteSet *VoteSet) addVote(vote *Vote, execVoteVerify func(chainID string, 169 pub crypto.PubKey) (err error)) (added bool, err error) { 170 if vote == nil { 171 return false, ErrVoteNil 172 } 173 valIndex := vote.ValidatorIndex 174 valAddr := vote.ValidatorAddress 175 blockKey := vote.BlockID.Key() 176 177 // Ensure that validator index was set 178 if valIndex < 0 { 179 return false, fmt.Errorf("index < 0: %w", ErrVoteInvalidValidatorIndex) 180 } else if len(valAddr) == 0 { 181 return false, fmt.Errorf("empty address: %w", ErrVoteInvalidValidatorAddress) 182 } 183 184 // Make sure the step matches. 185 if (vote.Height != voteSet.height) || 186 (vote.Round != voteSet.round) || 187 (vote.Type != voteSet.signedMsgType) { 188 return false, fmt.Errorf("expected %d/%d/%d, but got %d/%d/%d: %w", 189 voteSet.height, voteSet.round, voteSet.signedMsgType, 190 vote.Height, vote.Round, vote.Type, ErrVoteUnexpectedStep) 191 } 192 193 // Ensure that signer is a validator. 194 lookupAddr, val := voteSet.valSet.GetByIndex(valIndex) 195 if val == nil { 196 return false, fmt.Errorf( 197 "cannot find validator %d in valSet of size %d: %w", 198 valIndex, voteSet.valSet.Size(), ErrVoteInvalidValidatorIndex) 199 } 200 201 // Ensure that the signer has the right address. 202 if !bytes.Equal(valAddr, lookupAddr) { 203 return false, fmt.Errorf( 204 "vote.ValidatorAddress (%X) does not match address (%X) for vote.ValidatorIndex (%d)\n"+ 205 "Ensure the genesis file is correct across all validators: %w", 206 valAddr, lookupAddr, valIndex, ErrVoteInvalidValidatorAddress) 207 } 208 209 // If we already know of this vote, return false. 210 if existing, ok := voteSet.getVote(valIndex, blockKey); ok { 211 if bytes.Equal(existing.Signature, vote.Signature) { 212 return false, nil // duplicate 213 } 214 return false, fmt.Errorf("existing vote: %v; new vote: %v: %w", existing, vote, ErrVoteNonDeterministicSignature) 215 } 216 217 // Check signature. 218 if err := execVoteVerify(voteSet.chainID, val.PubKey); err != nil { 219 return false, fmt.Errorf( 220 "failed to verify vote with ChainID %s and PubKey %s: %w", 221 voteSet.chainID, 222 val.PubKey, 223 err, 224 ) 225 } 226 227 // Add vote and get conflicting vote if any. 228 added, conflicting := voteSet.addVerifiedVote(vote, blockKey, val.VotingPower) 229 if conflicting != nil { 230 return added, NewConflictingVoteError(conflicting, vote) 231 } 232 if !added { 233 panic("Expected to add non-conflicting vote") 234 } 235 return added, nil 236 } 237 238 // Returns (vote, true) if vote exists for valIndex and blockKey. 239 func (voteSet *VoteSet) getVote(valIndex int32, blockKey string) (vote *Vote, ok bool) { 240 if existing := voteSet.votes[valIndex]; existing != nil && existing.BlockID.Key() == blockKey { 241 return existing, true 242 } 243 if existing := voteSet.votesByBlock[blockKey].getByIndex(valIndex); existing != nil { 244 return existing, true 245 } 246 return nil, false 247 } 248 249 // Assumes signature is valid. 250 // If conflicting vote exists, returns it. 251 func (voteSet *VoteSet) addVerifiedVote( 252 vote *Vote, 253 blockKey string, 254 votingPower int64, 255 ) (added bool, conflicting *Vote) { 256 valIndex := vote.ValidatorIndex 257 258 // Already exists in voteSet.votes? 259 if existing := voteSet.votes[valIndex]; existing != nil { 260 if existing.BlockID.Equals(vote.BlockID) { 261 panic("addVerifiedVote does not expect duplicate votes") 262 } else { 263 conflicting = existing 264 } 265 // Replace vote if blockKey matches voteSet.maj23. 266 if voteSet.maj23 != nil && voteSet.maj23.Key() == blockKey { 267 voteSet.votes[valIndex] = vote 268 voteSet.votesBitArray.SetIndex(int(valIndex), true) 269 } 270 // Otherwise don't add it to voteSet.votes 271 } else { 272 // Add to voteSet.votes and incr .sum 273 voteSet.votes[valIndex] = vote 274 voteSet.votesBitArray.SetIndex(int(valIndex), true) 275 voteSet.sum += votingPower 276 } 277 278 votesByBlock, ok := voteSet.votesByBlock[blockKey] 279 if ok { 280 if conflicting != nil && !votesByBlock.peerMaj23 { 281 // There's a conflict and no peer claims that this block is special. 282 return false, conflicting 283 } 284 // We'll add the vote in a bit. 285 } else { 286 // .votesByBlock doesn't exist... 287 if conflicting != nil { 288 // ... and there's a conflicting vote. 289 // We're not even tracking this blockKey, so just forget it. 290 return false, conflicting 291 } 292 // ... and there's no conflicting vote. 293 // Start tracking this blockKey 294 votesByBlock = newBlockVotes(false, voteSet.valSet.Size()) 295 voteSet.votesByBlock[blockKey] = votesByBlock 296 // We'll add the vote in a bit. 297 } 298 299 // Before adding to votesByBlock, see if we'll exceed quorum 300 origSum := votesByBlock.sum 301 quorum := voteSet.valSet.TotalVotingPower()*2/3 + 1 302 303 // Add vote to votesByBlock 304 votesByBlock.addVerifiedVote(vote, votingPower) 305 306 // If we just crossed the quorum threshold and have 2/3 majority... 307 if origSum < quorum && quorum <= votesByBlock.sum { 308 // Only consider the first quorum reached 309 if voteSet.maj23 == nil { 310 maj23BlockID := vote.BlockID 311 voteSet.maj23 = &maj23BlockID 312 // And also copy votes over to voteSet.votes 313 for i, vote := range votesByBlock.votes { 314 if vote != nil { 315 voteSet.votes[i] = vote 316 } 317 } 318 } 319 } 320 321 return true, conflicting 322 } 323 324 // If a peer claims that it has 2/3 majority for given blockKey, call this. 325 // NOTE: if there are too many peers, or too much peer churn, 326 // this can cause memory issues. 327 // TODO: implement ability to remove peers too 328 // NOTE: VoteSet must not be nil 329 func (voteSet *VoteSet) SetPeerMaj23(peerID P2PID, blockID BlockID) error { 330 if voteSet == nil { 331 panic("SetPeerMaj23() on nil VoteSet") 332 } 333 voteSet.mtx.Lock() 334 defer voteSet.mtx.Unlock() 335 336 blockKey := blockID.Key() 337 338 // Make sure peer hasn't already told us something. 339 if existing, ok := voteSet.peerMaj23s[peerID]; ok { 340 if existing.Equals(blockID) { 341 return nil // Nothing to do 342 } 343 return fmt.Errorf("setPeerMaj23: Received conflicting blockID from peer %v. Got %v, expected %v", 344 peerID, blockID, existing) 345 } 346 voteSet.peerMaj23s[peerID] = blockID 347 348 // Create .votesByBlock entry if needed. 349 votesByBlock, ok := voteSet.votesByBlock[blockKey] 350 if ok { 351 if votesByBlock.peerMaj23 { 352 return nil // Nothing to do 353 } 354 votesByBlock.peerMaj23 = true 355 // No need to copy votes, already there. 356 } else { 357 votesByBlock = newBlockVotes(true, voteSet.valSet.Size()) 358 voteSet.votesByBlock[blockKey] = votesByBlock 359 // No need to copy votes, no votes to copy over. 360 } 361 return nil 362 } 363 364 // Implements VoteSetReader. 365 func (voteSet *VoteSet) BitArray() *bits.BitArray { 366 if voteSet == nil { 367 return nil 368 } 369 voteSet.mtx.Lock() 370 defer voteSet.mtx.Unlock() 371 return voteSet.votesBitArray.Copy() 372 } 373 374 func (voteSet *VoteSet) BitArrayByBlockID(blockID BlockID) *bits.BitArray { 375 if voteSet == nil { 376 return nil 377 } 378 voteSet.mtx.Lock() 379 defer voteSet.mtx.Unlock() 380 votesByBlock, ok := voteSet.votesByBlock[blockID.Key()] 381 if ok { 382 return votesByBlock.bitArray.Copy() 383 } 384 return nil 385 } 386 387 // NOTE: if validator has conflicting votes, returns "canonical" vote 388 // Implements VoteSetReader. 389 func (voteSet *VoteSet) GetByIndex(valIndex int32) *Vote { 390 if voteSet == nil { 391 return nil 392 } 393 voteSet.mtx.Lock() 394 defer voteSet.mtx.Unlock() 395 return voteSet.votes[valIndex] 396 } 397 398 // List returns a copy of the list of votes stored by the VoteSet. 399 func (voteSet *VoteSet) List() []Vote { 400 if voteSet == nil || voteSet.votes == nil { 401 return nil 402 } 403 votes := make([]Vote, 0, len(voteSet.votes)) 404 for i := range voteSet.votes { 405 if voteSet.votes[i] != nil { 406 votes = append(votes, *voteSet.votes[i]) 407 } 408 } 409 return votes 410 } 411 412 func (voteSet *VoteSet) GetByAddress(address []byte) *Vote { 413 if voteSet == nil { 414 return nil 415 } 416 voteSet.mtx.Lock() 417 defer voteSet.mtx.Unlock() 418 valIndex, val := voteSet.valSet.GetByAddress(address) 419 if val == nil { 420 panic("GetByAddress(address) returned nil") 421 } 422 return voteSet.votes[valIndex] 423 } 424 425 func (voteSet *VoteSet) HasTwoThirdsMajority() bool { 426 if voteSet == nil { 427 return false 428 } 429 voteSet.mtx.Lock() 430 defer voteSet.mtx.Unlock() 431 return voteSet.maj23 != nil 432 } 433 434 // Implements VoteSetReader. 435 func (voteSet *VoteSet) IsCommit() bool { 436 if voteSet == nil { 437 return false 438 } 439 if voteSet.signedMsgType != tmproto.PrecommitType { 440 return false 441 } 442 voteSet.mtx.Lock() 443 defer voteSet.mtx.Unlock() 444 return voteSet.maj23 != nil 445 } 446 447 func (voteSet *VoteSet) HasTwoThirdsAny() bool { 448 if voteSet == nil { 449 return false 450 } 451 voteSet.mtx.Lock() 452 defer voteSet.mtx.Unlock() 453 return voteSet.sum > voteSet.valSet.TotalVotingPower()*2/3 454 } 455 456 func (voteSet *VoteSet) HasAll() bool { 457 if voteSet == nil { 458 return false 459 } 460 voteSet.mtx.Lock() 461 defer voteSet.mtx.Unlock() 462 return voteSet.sum == voteSet.valSet.TotalVotingPower() 463 } 464 465 // If there was a +2/3 majority for blockID, return blockID and true. 466 // Else, return the empty BlockID{} and false. 467 func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) { 468 if voteSet == nil { 469 return BlockID{}, false 470 } 471 voteSet.mtx.Lock() 472 defer voteSet.mtx.Unlock() 473 if voteSet.maj23 != nil { 474 return *voteSet.maj23, true 475 } 476 return BlockID{}, false 477 } 478 479 //-------------------------------------------------------------------------------- 480 // Strings and JSON 481 482 const nilVoteSetString = "nil-VoteSet" 483 484 // String returns a string representation of VoteSet. 485 // 486 // See StringIndented. 487 func (voteSet *VoteSet) String() string { 488 if voteSet == nil { 489 return nilVoteSetString 490 } 491 return voteSet.StringIndented("") 492 } 493 494 // StringIndented returns an indented String. 495 // 496 // Height Round Type 497 // Votes 498 // Votes bit array 499 // 2/3+ majority 500 // 501 // See Vote#String. 502 func (voteSet *VoteSet) StringIndented(indent string) string { 503 voteSet.mtx.Lock() 504 defer voteSet.mtx.Unlock() 505 506 voteStrings := make([]string, len(voteSet.votes)) 507 for i, vote := range voteSet.votes { 508 if vote == nil { 509 voteStrings[i] = nilVoteStr 510 } else { 511 voteStrings[i] = vote.String() 512 } 513 } 514 return fmt.Sprintf(`VoteSet{ 515 %s H:%v R:%v T:%v 516 %s %v 517 %s %v 518 %s %v 519 %s}`, 520 indent, voteSet.height, voteSet.round, voteSet.signedMsgType, 521 indent, strings.Join(voteStrings, "\n"+indent+" "), 522 indent, voteSet.votesBitArray, 523 indent, voteSet.peerMaj23s, 524 indent) 525 } 526 527 // Marshal the VoteSet to JSON. Same as String(), just in JSON, 528 // and without the height/round/signedMsgType (since its already included in the votes). 529 func (voteSet *VoteSet) MarshalJSON() ([]byte, error) { 530 voteSet.mtx.Lock() 531 defer voteSet.mtx.Unlock() 532 return tmjson.Marshal(VoteSetJSON{ 533 voteSet.voteStrings(), 534 voteSet.bitArrayString(), 535 voteSet.peerMaj23s, 536 }) 537 } 538 539 // More human readable JSON of the vote set 540 // NOTE: insufficient for unmarshalling from (compressed votes) 541 // TODO: make the peerMaj23s nicer to read (eg just the block hash) 542 type VoteSetJSON struct { 543 Votes []string `json:"votes"` 544 VotesBitArray string `json:"votes_bit_array"` 545 PeerMaj23s map[P2PID]BlockID `json:"peer_maj_23s"` 546 } 547 548 // Return the bit-array of votes including 549 // the fraction of power that has voted like: 550 // "BA{29:xx__x__x_x___x__x_______xxx__} 856/1304 = 0.66" 551 func (voteSet *VoteSet) BitArrayString() string { 552 voteSet.mtx.Lock() 553 defer voteSet.mtx.Unlock() 554 return voteSet.bitArrayString() 555 } 556 557 func (voteSet *VoteSet) bitArrayString() string { 558 bAString := voteSet.votesBitArray.String() 559 voted, total, fracVoted := voteSet.sumTotalFrac() 560 return fmt.Sprintf("%s %d/%d = %.2f", bAString, voted, total, fracVoted) 561 } 562 563 // Returns a list of votes compressed to more readable strings. 564 func (voteSet *VoteSet) VoteStrings() []string { 565 voteSet.mtx.Lock() 566 defer voteSet.mtx.Unlock() 567 return voteSet.voteStrings() 568 } 569 570 func (voteSet *VoteSet) voteStrings() []string { 571 voteStrings := make([]string, len(voteSet.votes)) 572 for i, vote := range voteSet.votes { 573 if vote == nil { 574 voteStrings[i] = nilVoteStr 575 } else { 576 voteStrings[i] = vote.String() 577 } 578 } 579 return voteStrings 580 } 581 582 // StringShort returns a short representation of VoteSet. 583 // 584 // 1. height 585 // 2. round 586 // 3. signed msg type 587 // 4. first 2/3+ majority 588 // 5. fraction of voted power 589 // 6. votes bit array 590 // 7. 2/3+ majority for each peer 591 func (voteSet *VoteSet) StringShort() string { 592 if voteSet == nil { 593 return nilVoteSetString 594 } 595 voteSet.mtx.Lock() 596 defer voteSet.mtx.Unlock() 597 _, _, frac := voteSet.sumTotalFrac() 598 return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v +2/3:%v(%v) %v %v}`, 599 voteSet.height, voteSet.round, voteSet.signedMsgType, voteSet.maj23, frac, voteSet.votesBitArray, voteSet.peerMaj23s) 600 } 601 602 // LogString produces a logging suitable string representation of the 603 // vote set. 604 func (voteSet *VoteSet) LogString() string { 605 if voteSet == nil { 606 return nilVoteSetString 607 } 608 voteSet.mtx.Lock() 609 defer voteSet.mtx.Unlock() 610 voted, total, frac := voteSet.sumTotalFrac() 611 612 return fmt.Sprintf("Votes:%d/%d(%.3f)", voted, total, frac) 613 } 614 615 // return the power voted, the total, and the fraction 616 func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) { 617 voted, total := voteSet.sum, voteSet.valSet.TotalVotingPower() 618 fracVoted := float64(voted) / float64(total) 619 return voted, total, fracVoted 620 } 621 622 //-------------------------------------------------------------------------------- 623 // Commit 624 625 // MakeCommit constructs a Commit from the VoteSet. It only includes precommits 626 // for the block, which has 2/3+ majority, and nil. 627 // 628 // Panics if the vote type is not PrecommitType or if there's no +2/3 votes for 629 // a single block. 630 func (voteSet *VoteSet) MakeCommit() *Commit { 631 if voteSet.signedMsgType != tmproto.PrecommitType { 632 panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType") 633 } 634 voteSet.mtx.Lock() 635 defer voteSet.mtx.Unlock() 636 637 // Make sure we have a 2/3 majority 638 if voteSet.maj23 == nil { 639 panic("Cannot MakeCommit() unless a blockhash has +2/3") 640 } 641 642 // For every validator, get the precommit 643 commitSigs := make([]CommitSig, len(voteSet.votes)) 644 for i, v := range voteSet.votes { 645 commitSig := v.CommitSig() 646 // if block ID exists but doesn't match, exclude sig 647 if commitSig.ForBlock() && !v.BlockID.Equals(*voteSet.maj23) { 648 commitSig = NewCommitSigAbsent() 649 } 650 commitSigs[i] = commitSig 651 } 652 newCommit := NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs) 653 654 return newCommit 655 } 656 657 //-------------------------------------------------------------------------------- 658 659 /* 660 Votes for a particular block 661 There are two ways a *blockVotes gets created for a blockKey. 662 1. first (non-conflicting) vote of a validator w/ blockKey (peerMaj23=false) 663 2. A peer claims to have a 2/3 majority w/ blockKey (peerMaj23=true) 664 */ 665 type blockVotes struct { 666 peerMaj23 bool // peer claims to have maj23 667 bitArray *bits.BitArray // valIndex -> hasVote? 668 votes []*Vote // valIndex -> *Vote 669 sum int64 // vote sum 670 } 671 672 func newBlockVotes(peerMaj23 bool, numValidators int) *blockVotes { 673 return &blockVotes{ 674 peerMaj23: peerMaj23, 675 bitArray: bits.NewBitArray(numValidators), 676 votes: make([]*Vote, numValidators), 677 sum: 0, 678 } 679 } 680 681 func (vs *blockVotes) addVerifiedVote(vote *Vote, votingPower int64) { 682 valIndex := vote.ValidatorIndex 683 if existing := vs.votes[valIndex]; existing == nil { 684 vs.bitArray.SetIndex(int(valIndex), true) 685 vs.votes[valIndex] = vote 686 vs.sum += votingPower 687 } 688 } 689 690 func (vs *blockVotes) getByIndex(index int32) *Vote { 691 if vs == nil { 692 return nil 693 } 694 return vs.votes[index] 695 } 696 697 //-------------------------------------------------------------------------------- 698 699 // Common interface between *consensus.VoteSet and types.Commit 700 type VoteSetReader interface { 701 GetHeight() int64 702 GetRound() int32 703 Type() byte 704 Size() int 705 BitArray() *bits.BitArray 706 GetByIndex(int32) *Vote 707 IsCommit() bool 708 }