Browse Source

Use BlockID everywhere

pull/314/head
Jae Kwon 8 years ago
committed by Ethan Buchman
parent
commit
1173a85c85
13 changed files with 162 additions and 181 deletions
  1. +4
    -1
      blockchain/reactor.go
  2. +9
    -10
      consensus/common_test.go
  3. +1
    -1
      consensus/height_vote_set_test.go
  4. +1
    -1
      consensus/replay.go
  5. +27
    -29
      consensus/state.go
  6. +3
    -4
      state/execution.go
  7. +3
    -6
      state/state.go
  8. +55
    -31
      types/block.go
  9. +1
    -1
      types/part_set.go
  10. +2
    -6
      types/validator_set.go
  11. +4
    -12
      types/vote.go
  12. +17
    -42
      types/vote_set.go
  13. +35
    -37
      types/vote_set_test.go

+ 4
- 1
blockchain/reactor.go View File

@ -222,8 +222,11 @@ FOR_LOOP:
firstParts := first.MakePartSet()
firstPartsHeader := firstParts.Header()
// Finally, verify the first block using the second's commit
// NOTE: we can probably make this more efficient, but note that calling
// first.Hash() doesn't verify the tx contents, so MakePartSet() is
// currently necessary.
err := bcR.state.Validators.VerifyCommit(
bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.LastCommit)
bcR.state.ChainID, types.BlockID{first.Hash(), firstPartsHeader}, first.Height, second.LastCommit)
if err != nil {
log.Info("error in validation", "error", err)
bcR.pool.RedoRequest(first.Height)


+ 9
- 10
consensus/common_test.go View File

@ -45,8 +45,7 @@ func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartS
Height: vs.Height,
Round: vs.Round,
Type: voteType,
BlockHash: hash,
BlockPartsHeader: header,
BlockID: types.BlockID{hash, header},
}
err := vs.PrivValidator.SignVote(config.GetString("chain_id"), vote)
return vote, err
@ -127,12 +126,12 @@ func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *valid
panic("Failed to find prevote from validator")
}
if blockHash == nil {
if vote.BlockHash != nil {
panic(fmt.Sprintf("Expected prevote to be for nil, got %X", vote.BlockHash))
if vote.BlockID.Hash != nil {
panic(fmt.Sprintf("Expected prevote to be for nil, got %X", vote.BlockID.Hash))
}
} else {
if !bytes.Equal(vote.BlockHash, blockHash) {
panic(fmt.Sprintf("Expected prevote to be for %X, got %X", blockHash, vote.BlockHash))
if !bytes.Equal(vote.BlockID.Hash, blockHash) {
panic(fmt.Sprintf("Expected prevote to be for %X, got %X", blockHash, vote.BlockID.Hash))
}
}
}
@ -143,8 +142,8 @@ func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorS
if vote = votes.GetByAddress(privVal.Address); vote == nil {
panic("Failed to find precommit from validator")
}
if !bytes.Equal(vote.BlockHash, blockHash) {
panic(fmt.Sprintf("Expected precommit to be for %X, got %X", blockHash, vote.BlockHash))
if !bytes.Equal(vote.BlockID.Hash, blockHash) {
panic(fmt.Sprintf("Expected precommit to be for %X, got %X", blockHash, vote.BlockID.Hash))
}
}
@ -156,11 +155,11 @@ func validatePrecommit(t *testing.T, cs *ConsensusState, thisRound, lockRound in
}
if votedBlockHash == nil {
if vote.BlockHash != nil {
if vote.BlockID.Hash != nil {
panic("Expected precommit to be for nil")
}
} else {
if !bytes.Equal(vote.BlockHash, votedBlockHash) {
if !bytes.Equal(vote.BlockID.Hash, votedBlockHash) {
panic("Expected precommit to be for proposal block")
}
}


+ 1
- 1
consensus/height_vote_set_test.go View File

@ -44,7 +44,7 @@ func makeVoteHR(t *testing.T, height, round int, privVals []*types.PrivValidator
Height: height,
Round: round,
Type: types.VoteTypePrecommit,
BlockHash: []byte("fakehash"),
BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}},
}
chainID := config.GetString("chain_id")
err := privVal.SignVote(chainID, vote)


+ 1
- 1
consensus/replay.go View File

@ -68,7 +68,7 @@ func (cs *ConsensusState) readReplayMessage(msgBytes []byte, newStepCh chan inte
case *VoteMessage:
v := msg.Vote
log.Notice("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type,
"hash", v.BlockHash, "header", v.BlockPartsHeader, "peer", peerKey)
"blockID", v.BlockID, "peer", peerKey)
}
cs.handleMsg(m, cs.RoundState)


+ 27
- 29
consensus/state.go View File

@ -476,7 +476,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
if precommit == nil {
continue
}
// XXXX reconstruct Vote from precommit after changing precommit to simpler
// XXX reconstruct Vote from precommit after changing precommit to simpler
// structure.
added, err := lastPrecommits.AddVote(precommit)
if !added || err != nil {
@ -922,8 +922,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
Height: cs.Height,
Time: time.Now(),
NumTxs: len(txs),
LastBlockHash: cs.state.LastBlockHash,
LastBlockParts: cs.state.LastBlockParts,
LastBlockID: cs.state.LastBlockID,
ValidatorsHash: cs.state.Validators.Hash(),
AppHash: cs.state.AppHash, // state merkle root of txs from the previous block.
},
@ -1048,7 +1047,7 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) {
cs.newStep()
}()
hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()
// If we don't have a polka, we must precommit nil
if !ok {
@ -1070,7 +1069,7 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) {
}
// +2/3 prevoted nil. Unlock and precommit nil.
if len(hash) == 0 {
if len(blockID.Hash) == 0 {
if cs.LockedBlock == nil {
log.Notice("enterPrecommit: +2/3 prevoted for nil.")
} else {
@ -1087,17 +1086,17 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) {
// At this point, +2/3 prevoted for a particular block.
// If we're already locked on that block, precommit it, and update the LockedRound
if cs.LockedBlock.HashesTo(hash) {
if cs.LockedBlock.HashesTo(blockID.Hash) {
log.Notice("enterPrecommit: +2/3 prevoted locked block. Relocking")
cs.LockedRound = round
types.FireEventRelock(cs.evsw, cs.RoundStateEvent())
cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader)
cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader)
return
}
// If +2/3 prevoted for proposal block, stage and precommit it
if cs.ProposalBlock.HashesTo(hash) {
log.Notice("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", hash)
if cs.ProposalBlock.HashesTo(blockID.Hash) {
log.Notice("enterPrecommit: +2/3 prevoted proposal block. Locking", "hash", blockID.Hash)
// Validate the block.
if err := cs.state.ValidateBlock(cs.ProposalBlock); err != nil {
PanicConsensus(Fmt("enterPrecommit: +2/3 prevoted for an invalid block: %v", err))
@ -1106,7 +1105,7 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) {
cs.LockedBlock = cs.ProposalBlock
cs.LockedBlockParts = cs.ProposalBlockParts
types.FireEventLock(cs.evsw, cs.RoundStateEvent())
cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader)
cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader)
return
}
@ -1117,9 +1116,9 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) {
cs.LockedRound = 0
cs.LockedBlock = nil
cs.LockedBlockParts = nil
if !cs.ProposalBlockParts.HasHeader(partsHeader) {
if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
cs.ProposalBlock = nil
cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader)
cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader)
}
types.FireEventUnlock(cs.evsw, cs.RoundStateEvent())
cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
@ -1167,7 +1166,7 @@ func (cs *ConsensusState) enterCommit(height int, commitRound int) {
cs.tryFinalizeCommit(height)
}()
hash, partsHeader, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority()
blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority()
if !ok {
PanicSanity("RunActionCommit() expects +2/3 precommits")
}
@ -1175,18 +1174,18 @@ func (cs *ConsensusState) enterCommit(height int, commitRound int) {
// The Locked* fields no longer matter.
// Move them over to ProposalBlock if they match the commit hash,
// otherwise they'll be cleared in updateToState.
if cs.LockedBlock.HashesTo(hash) {
if cs.LockedBlock.HashesTo(blockID.Hash) {
cs.ProposalBlock = cs.LockedBlock
cs.ProposalBlockParts = cs.LockedBlockParts
}
// If we don't have the block being committed, set up to get it.
if !cs.ProposalBlock.HashesTo(hash) {
if !cs.ProposalBlockParts.HasHeader(partsHeader) {
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) {
// We're getting the wrong block.
// Set up ProposalBlockParts and keep waiting.
cs.ProposalBlock = nil
cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader)
cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader)
} else {
// We just need to keep waiting.
}
@ -1199,12 +1198,12 @@ func (cs *ConsensusState) tryFinalizeCommit(height int) {
PanicSanity(Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
}
hash, _, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
if !ok || len(hash) == 0 {
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
if !ok || len(blockID.Hash) == 0 {
log.Warn("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.")
return
}
if !cs.ProposalBlock.HashesTo(hash) {
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
// TODO: this happens every time if we're not a validator (ugly logs)
// TODO: ^^ wait, why does it matter that we're a validator?
log.Warn("Attempt to finalize failed. We don't have the commit block.")
@ -1221,16 +1220,16 @@ func (cs *ConsensusState) finalizeCommit(height int) {
return
}
hash, header, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts
if !ok {
PanicSanity(Fmt("Cannot finalizeCommit, commit does not have two thirds majority"))
}
if !blockParts.HasHeader(header) {
if !blockParts.HasHeader(blockID.PartsHeader) {
PanicSanity(Fmt("Expected ProposalBlockParts header to be commit header"))
}
if !block.HashesTo(hash) {
if !block.HashesTo(blockID.Hash) {
PanicSanity(Fmt("Cannot finalizeCommit, ProposalBlock does not hash to commit hash"))
}
if err := cs.state.ValidateBlock(block); err != nil {
@ -1461,8 +1460,8 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool,
// we'll still enterNewRound(H,vote.R) and enterPrecommit(H,vote.R) to process it
// there.
if (cs.LockedBlock != nil) && (cs.LockedRound < vote.Round) && (vote.Round <= cs.Round) {
hash, _, ok := prevotes.TwoThirdsMajority()
if ok && !cs.LockedBlock.HashesTo(hash) {
blockID, ok := prevotes.TwoThirdsMajority()
if ok && !cs.LockedBlock.HashesTo(blockID.Hash) {
log.Notice("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round)
cs.LockedRound = 0
cs.LockedBlock = nil
@ -1488,9 +1487,9 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool,
case types.VoteTypePrecommit:
precommits := cs.Votes.Precommits(vote.Round)
log.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort())
hash, _, ok := precommits.TwoThirdsMajority()
blockID, ok := precommits.TwoThirdsMajority()
if ok {
if len(hash) == 0 {
if len(blockID.Hash) == 0 {
cs.enterNewRound(height, vote.Round+1)
} else {
cs.enterNewRound(height, vote.Round)
@ -1527,8 +1526,7 @@ func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSet
Height: cs.Height,
Round: cs.Round,
Type: type_,
BlockHash: hash,
BlockPartsHeader: header,
BlockID: types.BlockID{hash, header},
}
err := cs.privValidator.SignVote(cs.state.ChainID, vote)
return vote, err


+ 3
- 4
state/execution.go View File

@ -43,8 +43,7 @@ func (s *State) ExecBlock(eventCache types.Fireable, proxyAppConn proxy.AppConnC
// All good!
nextValSet.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockID = types.BlockID{block.Hash(), blockPartsHeader}
s.LastBlockTime = block.Time
s.Validators = nextValSet
s.LastValidators = valSet
@ -119,7 +118,7 @@ func (s *State) execBlockOnProxyApp(eventCache types.Fireable, proxyAppConn prox
func (s *State) validateBlock(block *types.Block) error {
// Basic block validation.
err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime, s.AppHash)
err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash)
if err != nil {
return err
}
@ -135,7 +134,7 @@ func (s *State) validateBlock(block *types.Block) error {
s.LastValidators.Size(), len(block.LastCommit.Precommits))
}
err := s.LastValidators.VerifyCommit(
s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastCommit)
s.ChainID, s.LastBlockID, block.Height-1, block.LastCommit)
if err != nil {
return err
}


+ 3
- 6
state/state.go View File

@ -25,8 +25,7 @@ type State struct {
GenesisDoc *types.GenesisDoc
ChainID string
LastBlockHeight int // Genesis state has this set to 0. So, Block(H=0) does not exist.
LastBlockHash []byte
LastBlockParts types.PartSetHeader
LastBlockID types.BlockID
LastBlockTime time.Time
Validators *types.ValidatorSet
LastValidators *types.ValidatorSet
@ -56,8 +55,7 @@ func (s *State) Copy() *State {
GenesisDoc: s.GenesisDoc,
ChainID: s.ChainID,
LastBlockHeight: s.LastBlockHeight,
LastBlockHash: s.LastBlockHash,
LastBlockParts: s.LastBlockParts,
LastBlockID: s.LastBlockID,
LastBlockTime: s.LastBlockTime,
Validators: s.Validators.Copy(),
LastValidators: s.LastValidators.Copy(),
@ -117,8 +115,7 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State {
GenesisDoc: genDoc,
ChainID: genDoc.ChainID,
LastBlockHeight: 0,
LastBlockHash: nil,
LastBlockParts: types.PartSetHeader{},
LastBlockID: types.BlockID{},
LastBlockTime: genDoc.GenesisTime,
Validators: types.NewValidatorSet(validators),
LastValidators: types.NewValidatorSet(nil),


+ 55
- 31
types/block.go View File

@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"time"
@ -21,8 +22,8 @@ type Block struct {
}
// Basic validation that doesn't involve state data.
func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash []byte,
lastBlockParts PartSetHeader, lastBlockTime time.Time, appHash []byte) error {
func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockID BlockID,
lastBlockTime time.Time, appHash []byte) error {
if b.ChainID != chainID {
return errors.New(Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID))
}
@ -39,11 +40,8 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash
if b.NumTxs != len(b.Data.Txs) {
return errors.New(Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", len(b.Data.Txs), b.NumTxs))
}
if !bytes.Equal(b.LastBlockHash, lastBlockHash) {
return errors.New(Fmt("Wrong Block.Header.LastBlockHash. Expected %X, got %X", lastBlockHash, b.LastBlockHash))
}
if !b.LastBlockParts.Equals(lastBlockParts) {
return errors.New(Fmt("Wrong Block.Header.LastBlockParts. Expected %v, got %v", lastBlockParts, b.LastBlockParts))
if !b.LastBlockID.Equals(lastBlockID) {
return errors.New(Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID))
}
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return errors.New(Fmt("Wrong Block.Header.LastCommitHash. Expected %X, got %X", b.LastCommitHash, b.LastCommit.Hash()))
@ -130,16 +128,15 @@ func (b *Block) StringShort() string {
//-----------------------------------------------------------------------------
type Header struct {
ChainID string `json:"chain_id"`
Height int `json:"height"`
Time time.Time `json:"time"`
NumTxs int `json:"num_txs"`
LastBlockHash []byte `json:"last_block_hash"`
LastBlockParts PartSetHeader `json:"last_block_parts"`
LastCommitHash []byte `json:"last_commit_hash"`
DataHash []byte `json:"data_hash"`
ValidatorsHash []byte `json:"validators_hash"`
AppHash []byte `json:"app_hash"` // state merkle root of txs from the previous block
ChainID string `json:"chain_id"`
Height int `json:"height"`
Time time.Time `json:"time"`
NumTxs int `json:"num_txs"`
LastBlockID BlockID `json:"last_block_id"`
LastCommitHash []byte `json:"last_commit_hash"`
DataHash []byte `json:"data_hash"`
ValidatorsHash []byte `json:"validators_hash"`
AppHash []byte `json:"app_hash"` // state merkle root of txs from the previous block
}
// NOTE: hash is nil if required fields are missing.
@ -148,16 +145,15 @@ func (h *Header) Hash() []byte {
return nil
}
return merkle.SimpleHashFromMap(map[string]interface{}{
"ChainID": h.ChainID,
"Height": h.Height,
"Time": h.Time,
"NumTxs": h.NumTxs,
"LastBlock": h.LastBlockHash,
"LastBlockParts": h.LastBlockParts,
"LastCommit": h.LastCommitHash,
"Data": h.DataHash,
"Validators": h.ValidatorsHash,
"App": h.AppHash,
"ChainID": h.ChainID,
"Height": h.Height,
"Time": h.Time,
"NumTxs": h.NumTxs,
"LastBlockID": h.LastBlockID,
"LastCommit": h.LastCommitHash,
"Data": h.DataHash,
"Validators": h.ValidatorsHash,
"App": h.AppHash,
})
}
@ -170,8 +166,7 @@ func (h *Header) StringIndented(indent string) string {
%s Height: %v
%s Time: %v
%s NumTxs: %v
%s LastBlock: %X
%s LastBlockParts: %v
%s LastBlockID: %v
%s LastCommit: %X
%s Data: %X
%s Validators: %X
@ -181,8 +176,7 @@ func (h *Header) StringIndented(indent string) string {
indent, h.Height,
indent, h.Time,
indent, h.NumTxs,
indent, h.LastBlockHash,
indent, h.LastBlockParts,
indent, h.LastBlockID,
indent, h.LastCommitHash,
indent, h.DataHash,
indent, h.ValidatorsHash,
@ -360,3 +354,33 @@ func (data *Data) StringIndented(indent string) string {
indent, strings.Join(txStrings, "\n"+indent+" "),
indent, data.hash)
}
//--------------------------------------------------------------------------------
type BlockID struct {
Hash []byte `json:"hash"`
PartsHeader PartSetHeader `json:"parts"`
}
func (blockID BlockID) IsZero() bool {
return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero()
}
func (blockID BlockID) Equals(other BlockID) bool {
return bytes.Equal(blockID.Hash, other.Hash) &&
blockID.PartsHeader.Equals(other.PartsHeader)
}
func (blockID BlockID) Key() string {
return string(blockID.Hash) + string(wire.BinaryBytes(blockID.PartsHeader))
}
func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) {
wire.WriteTo([]byte(Fmt(`{"hash":"%X","parts":`, blockID.Hash)), w, n, err)
blockID.PartsHeader.WriteSignBytes(w, n, err)
wire.WriteTo([]byte("}"), w, n, err)
}
func (blockID BlockID) String() string {
return fmt.Sprintf(`%X:%v`, blockID.Hash, blockID.PartsHeader)
}

+ 1
- 1
types/part_set.go View File

@ -66,7 +66,7 @@ type PartSetHeader struct {
}
func (psh PartSetHeader) String() string {
return fmt.Sprintf("PartSet{T:%v %X}", psh.Total, Fingerprint(psh.Hash))
return fmt.Sprintf("%v:%X", psh.Total, Fingerprint(psh.Hash))
}
func (psh PartSetHeader) IsZero() bool {


+ 2
- 6
types/validator_set.go View File

@ -206,8 +206,7 @@ func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
}
// Verify that +2/3 of the set had signed the given signBytes
func (valSet *ValidatorSet) VerifyCommit(chainID string,
hash []byte, parts PartSetHeader, height int, commit *Commit) error {
func (valSet *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int, commit *Commit) error {
if valSet.Size() != len(commit.Precommits) {
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", valSet.Size(), len(commit.Precommits))
}
@ -238,10 +237,7 @@ func (valSet *ValidatorSet) VerifyCommit(chainID string,
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
}
if !bytes.Equal(precommit.BlockHash, hash) {
continue // Not an error, but doesn't count
}
if !parts.Equals(precommit.BlockPartsHeader) {
if !blockID.Equals(precommit.BlockID) {
continue // Not an error, but doesn't count
}
// Good precommit!


+ 4
- 12
types/vote.go View File

@ -1,7 +1,6 @@
package types
import (
"bytes"
"errors"
"fmt"
"io"
@ -35,8 +34,7 @@ type Vote struct {
Height int `json:"height"`
Round int `json:"round"`
Type byte `json:"type"`
BlockHash []byte `json:"block_hash"` // empty if vote is nil.
BlockPartsHeader PartSetHeader `json:"block_parts_header"` // zero if vote is nil.
BlockID BlockID `json:"block_id"` // zero if vote is nil.
Signature crypto.SignatureEd25519 `json:"signature"`
}
@ -48,7 +46,8 @@ const (
func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
wire.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err)
wire.WriteTo([]byte(Fmt(`,"vote":{"block_hash":"%X","block_parts_header":%v`, vote.BlockHash, vote.BlockPartsHeader)), w, n, err)
wire.WriteTo([]byte(`,"vote":{"block_id":`), w, n, err)
vote.BlockID.WriteSignBytes(w, n, err)
wire.WriteTo([]byte(Fmt(`,"height":%v,"round":%v,"type":%v}}`, vote.Height, vote.Round, vote.Type)), w, n, err)
}
@ -74,12 +73,5 @@ func (vote *Vote) String() string {
return fmt.Sprintf("Vote{%v:%X %v/%02d/%v(%v) %X %v}",
vote.ValidatorIndex, Fingerprint(vote.ValidatorAddress),
vote.Height, vote.Round, vote.Type, typeString,
Fingerprint(vote.BlockHash), vote.Signature)
}
// Does not check signature, but checks for equality of block
// NOTE: May be from different validators, and signature may be incorrect.
func (vote *Vote) SameBlockAs(other *Vote) bool {
return bytes.Equal(vote.BlockHash, other.BlockHash) &&
vote.BlockPartsHeader.Equals(other.BlockPartsHeader)
Fingerprint(vote.BlockID.Hash), vote.Signature)
}

+ 17
- 42
types/vote_set.go View File

@ -7,7 +7,6 @@ import (
"sync"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
)
/*
@ -55,9 +54,9 @@ type VoteSet struct {
votesBitArray *BitArray
votes []*Vote // Primary votes to share
sum int64 // Sum of voting power for seen votes, discounting conflicts
maj23 *blockInfo // First 2/3 majority seen
maj23 *BlockID // First 2/3 majority seen
votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes
peerMaj23s map[string]*blockInfo // Maj23 for each peer
peerMaj23s map[string]BlockID // Maj23 for each peer
}
// Constructs a new VoteSet struct used to accumulate votes for given height/round.
@ -76,7 +75,7 @@ func NewVoteSet(chainID string, height int, round int, type_ byte, valSet *Valid
sum: 0,
maj23: nil,
votesByBlock: make(map[string]*blockVotes, valSet.Size()),
peerMaj23s: make(map[string]*blockInfo),
peerMaj23s: make(map[string]BlockID),
}
}
@ -134,7 +133,7 @@ func (voteSet *VoteSet) AddVote(vote *Vote) (added bool, err error) {
func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
valIndex := vote.ValidatorIndex
valAddr := vote.ValidatorAddress
blockKey := getBlockKey(vote)
blockKey := vote.BlockID.Key()
// Ensure that validator index was set
if valIndex < 0 || len(valAddr) == 0 {
@ -192,7 +191,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
// Returns (vote, true) if vote exists for valIndex and blockKey
func (voteSet *VoteSet) getVote(valIndex int, blockKey string) (vote *Vote, ok bool) {
if existing := voteSet.votes[valIndex]; existing != nil && getBlockKey(existing) == blockKey {
if existing := voteSet.votes[valIndex]; existing != nil && existing.BlockID.Key() == blockKey {
return existing, true
}
if existing := voteSet.votesByBlock[blockKey].getByIndex(valIndex); existing != nil {
@ -208,13 +207,13 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
// Already exists in voteSet.votes?
if existing := voteSet.votes[valIndex]; existing != nil {
if existing.SameBlockAs(vote) {
if existing.BlockID.Equals(vote.BlockID) {
PanicSanity("addVerifiedVote does not expect duplicate votes")
} else {
conflicting = existing
}
// Replace vote if blockKey matches voteSet.maj23.
if voteSet.maj23 != nil && voteSet.maj23.BlockKey() == blockKey {
if voteSet.maj23 != nil && voteSet.maj23.Key() == blockKey {
voteSet.votes[valIndex] = vote
voteSet.votesBitArray.SetIndex(valIndex, true)
}
@ -259,7 +258,8 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
if origSum < quorum && quorum <= votesByBlock.sum {
// Only consider the first quorum reached
if voteSet.maj23 == nil {
voteSet.maj23 = getBlockInfo(vote)
maj23BlockID := vote.BlockID
voteSet.maj23 = &maj23BlockID
// And also copy votes over to voteSet.votes
for i, vote := range votesByBlock.votes {
if vote != nil {
@ -276,22 +276,21 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower
// NOTE: if there are too many peers, or too much peer churn,
// this can cause memory issues.
// TODO: implement ability to remove peers too
func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockHash []byte, blockPartsHeader PartSetHeader) {
func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
blockInfo := &blockInfo{blockHash, blockPartsHeader}
blockKey := blockInfo.BlockKey()
blockKey := blockID.Key()
// Make sure peer hasn't already told us something.
if existing, ok := voteSet.peerMaj23s[peerID]; ok {
if existing.Equals(blockInfo) {
if existing.Equals(blockID) {
return // Nothing to do
} else {
return // TODO bad peer!
}
}
voteSet.peerMaj23s[peerID] = blockInfo
voteSet.peerMaj23s[peerID] = blockID
// Create .votesByBlock entry if needed.
votesByBlock, ok := voteSet.votesByBlock[blockKey]
@ -367,13 +366,13 @@ func (voteSet *VoteSet) HasTwoThirdsAny() bool {
// Returns either a blockhash (or nil) that received +2/3 majority.
// If there exists no such majority, returns (nil, PartSetHeader{}, false).
func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts PartSetHeader, ok bool) {
func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) {
voteSet.mtx.Lock()
defer voteSet.mtx.Unlock()
if voteSet.maj23 != nil {
return voteSet.maj23.hash, voteSet.maj23.partsHeader, true
return *voteSet.maj23, true
} else {
return nil, PartSetHeader{}, false
return BlockID{}, false
}
}
@ -430,7 +429,7 @@ func (voteSet *VoteSet) MakeCommit() *Commit {
}
// For every validator, get the precommit
maj23Votes := voteSet.votesByBlock[voteSet.maj23.BlockKey()]
maj23Votes := voteSet.votesByBlock[voteSet.maj23.Key()]
return &Commit{
Precommits: maj23Votes.votes,
}
@ -488,27 +487,3 @@ type VoteSetReader interface {
GetByIndex(int) *Vote
IsCommit() bool
}
//--------------------------------------------------------------------------------
type blockInfo struct {
hash []byte
partsHeader PartSetHeader
}
func (bInfo *blockInfo) Equals(other *blockInfo) bool {
return bytes.Equal(bInfo.hash, other.hash) &&
bInfo.partsHeader.Equals(other.partsHeader)
}
func (bInfo *blockInfo) BlockKey() string {
return string(bInfo.hash) + string(wire.BinaryBytes(bInfo.partsHeader))
}
func getBlockInfo(vote *Vote) *blockInfo {
return &blockInfo{vote.BlockHash, vote.BlockPartsHeader}
}
func getBlockKey(vote *Vote) string {
return string(vote.BlockHash) + string(wire.BinaryBytes(vote.BlockPartsHeader))
}

+ 35
- 37
types/vote_set_test.go View File

@ -49,14 +49,14 @@ func withType(vote *Vote, type_ byte) *Vote {
// Convenience: Return new vote with different blockHash
func withBlockHash(vote *Vote, blockHash []byte) *Vote {
vote = vote.Copy()
vote.BlockHash = blockHash
vote.BlockID.Hash = blockHash
return vote
}
// Convenience: Return new vote with different blockParts
func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote {
vote = vote.Copy()
vote.BlockPartsHeader = blockPartsHeader
vote.BlockID.PartsHeader = blockPartsHeader
return vote
}
@ -79,8 +79,8 @@ func TestAddVote(t *testing.T) {
if voteSet.BitArray().GetIndex(0) {
t.Errorf("Expected BitArray.GetIndex(0) to be false")
}
hash, header, ok := voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
@ -90,7 +90,7 @@ func TestAddVote(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
BlockHash: nil,
BlockID: BlockID{nil, PartSetHeader{}},
}
signAddVote(val0, vote, voteSet)
@ -100,8 +100,8 @@ func TestAddVote(t *testing.T) {
if !voteSet.BitArray().GetIndex(0) {
t.Errorf("Expected BitArray.GetIndex(0) to be true")
}
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
}
@ -116,15 +116,15 @@ func Test2_3Majority(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
BlockHash: nil,
BlockID: BlockID{nil, PartSetHeader{}},
}
// 6 out of 10 voted for nil.
for i := 0; i < 6; i++ {
vote := withValidator(voteProto, privValidators[i].Address, i)
signAddVote(privValidators[i], vote, voteSet)
}
hash, header, ok := voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
@ -132,8 +132,8 @@ func Test2_3Majority(t *testing.T) {
{
vote := withValidator(voteProto, privValidators[6].Address, 6)
signAddVote(privValidators[6], withBlockHash(vote, RandBytes(32)), voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
}
@ -142,8 +142,8 @@ func Test2_3Majority(t *testing.T) {
{
vote := withValidator(voteProto, privValidators[7].Address, 7)
signAddVote(privValidators[7], vote, voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || !ok {
blockID, ok = voteSet.TwoThirdsMajority()
if !ok || !blockID.IsZero() {
t.Errorf("There should be 2/3 majority for nil")
}
}
@ -163,8 +163,7 @@ func Test2_3MajorityRedux(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
BlockHash: blockHash,
BlockPartsHeader: blockPartsHeader,
BlockID: BlockID{blockHash, blockPartsHeader},
}
// 66 out of 100 voted for nil.
@ -172,8 +171,8 @@ func Test2_3MajorityRedux(t *testing.T) {
vote := withValidator(voteProto, privValidators[i].Address, i)
signAddVote(privValidators[i], vote, voteSet)
}
hash, header, ok := voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok := voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority")
}
@ -181,8 +180,8 @@ func Test2_3MajorityRedux(t *testing.T) {
{
vote := withValidator(voteProto, privValidators[66].Address, 66)
signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added was nil")
}
}
@ -192,8 +191,8 @@ func Test2_3MajorityRedux(t *testing.T) {
vote := withValidator(voteProto, privValidators[67].Address, 67)
blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
signAddVote(privValidators[67], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Hash")
}
}
@ -203,8 +202,8 @@ func Test2_3MajorityRedux(t *testing.T) {
vote := withValidator(voteProto, privValidators[68].Address, 68)
blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartsHeader.Hash}
signAddVote(privValidators[68], withBlockPartsHeader(vote, blockPartsHeader), voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different PartSetHeader Total")
}
}
@ -213,8 +212,8 @@ func Test2_3MajorityRedux(t *testing.T) {
{
vote := withValidator(voteProto, privValidators[69].Address, 69)
signAddVote(privValidators[69], withBlockHash(vote, RandBytes(32)), voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if hash != nil || !header.IsZero() || ok {
blockID, ok = voteSet.TwoThirdsMajority()
if ok || !blockID.IsZero() {
t.Errorf("There should be no 2/3 majority: last vote added had different BlockHash")
}
}
@ -223,8 +222,8 @@ func Test2_3MajorityRedux(t *testing.T) {
{
vote := withValidator(voteProto, privValidators[70].Address, 70)
signAddVote(privValidators[70], vote, voteSet)
hash, header, ok = voteSet.TwoThirdsMajority()
if !bytes.Equal(hash, blockHash) || !header.Equals(blockPartsHeader) || !ok {
blockID, ok = voteSet.TwoThirdsMajority()
if !ok || !blockID.Equals(BlockID{blockHash, blockPartsHeader}) {
t.Errorf("There should be 2/3 majority")
}
}
@ -240,7 +239,7 @@ func TestBadVotes(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
BlockHash: nil,
BlockID: BlockID{nil, PartSetHeader{}},
}
// val0 votes for nil.
@ -301,7 +300,7 @@ func TestConflicts(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrevote,
BlockHash: nil,
BlockID: BlockID{nil, PartSetHeader{}},
}
// val0 votes for nil.
@ -326,7 +325,7 @@ func TestConflicts(t *testing.T) {
}
// start tracking blockHash1
voteSet.SetPeerMaj23("peerA", blockHash1, PartSetHeader{})
voteSet.SetPeerMaj23("peerA", BlockID{blockHash1, PartSetHeader{}})
// val0 votes again for blockHash1.
{
@ -341,7 +340,7 @@ func TestConflicts(t *testing.T) {
}
// attempt tracking blockHash2, should fail because already set for peerA.
voteSet.SetPeerMaj23("peerA", blockHash2, PartSetHeader{})
voteSet.SetPeerMaj23("peerA", BlockID{blockHash2, PartSetHeader{}})
// val0 votes again for blockHash1.
{
@ -390,7 +389,7 @@ func TestConflicts(t *testing.T) {
}
// now attempt tracking blockHash1
voteSet.SetPeerMaj23("peerB", blockHash1, PartSetHeader{})
voteSet.SetPeerMaj23("peerB", BlockID{blockHash1, PartSetHeader{}})
// val2 votes for blockHash1.
{
@ -408,8 +407,8 @@ func TestConflicts(t *testing.T) {
if !voteSet.HasTwoThirdsMajority() {
t.Errorf("We should have 2/3 majority for blockHash1")
}
blockHash23maj, _, _ := voteSet.TwoThirdsMajority()
if !bytes.Equal(blockHash23maj, blockHash1) {
blockIDMaj23, _ := voteSet.TwoThirdsMajority()
if !bytes.Equal(blockIDMaj23.Hash, blockHash1) {
t.Errorf("Got the wrong 2/3 majority blockhash")
}
if !voteSet.HasTwoThirdsAny() {
@ -429,8 +428,7 @@ func TestMakeCommit(t *testing.T) {
Height: height,
Round: round,
Type: VoteTypePrecommit,
BlockHash: blockHash,
BlockPartsHeader: blockPartsHeader,
BlockID: BlockID{blockHash, blockPartsHeader},
}
// 6 out of 10 voted for some block.


Loading…
Cancel
Save