From 1173a85c85fb88e624c63625457b8970b55de550 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Tue, 16 Aug 2016 14:59:19 -0700 Subject: [PATCH] Use BlockID everywhere --- blockchain/reactor.go | 5 +- consensus/common_test.go | 19 ++++--- consensus/height_vote_set_test.go | 2 +- consensus/replay.go | 2 +- consensus/state.go | 56 ++++++++++---------- state/execution.go | 7 ++- state/state.go | 9 ++-- types/block.go | 86 ++++++++++++++++++++----------- types/part_set.go | 2 +- types/validator_set.go | 8 +-- types/vote.go | 16 ++---- types/vote_set.go | 59 ++++++--------------- types/vote_set_test.go | 72 +++++++++++++------------- 13 files changed, 162 insertions(+), 181 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index ae52c7738..5c42d1802 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -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) diff --git a/consensus/common_test.go b/consensus/common_test.go index 447112991..6126a3faf 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -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") } } diff --git a/consensus/height_vote_set_test.go b/consensus/height_vote_set_test.go index ab0fed30e..78733e415 100644 --- a/consensus/height_vote_set_test.go +++ b/consensus/height_vote_set_test.go @@ -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) diff --git a/consensus/replay.go b/consensus/replay.go index 54e3c134f..b9d02c17f 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -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) diff --git a/consensus/state.go b/consensus/state.go index af7f62d56..654ca8d16 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -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 .") 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 diff --git a/state/execution.go b/state/execution.go index b6bc215ee..e5653e7e7 100644 --- a/state/execution.go +++ b/state/execution.go @@ -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 } diff --git a/state/state.go b/state/state.go index 798e8ce72..213484863 100644 --- a/state/state.go +++ b/state/state.go @@ -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), diff --git a/types/block.go b/types/block.go index a6f7bb5f3..537075182 100644 --- a/types/block.go +++ b/types/block.go @@ -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) +} diff --git a/types/part_set.go b/types/part_set.go index bdf198d94..9a4b56cce 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -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 { diff --git a/types/validator_set.go b/types/validator_set.go index ed3ff1561..92400f67a 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -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! diff --git a/types/vote.go b/types/vote.go index 8a3253b13..4584b749b 100644 --- a/types/vote.go +++ b/types/vote.go @@ -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) } diff --git a/types/vote_set.go b/types/vote_set.go index 3d4299f76..4ee0588e9 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -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)) -} diff --git a/types/vote_set_test.go b/types/vote_set_test.go index bfc82c162..19523a825 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -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.