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