diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index d8e4bc82c..9acafce71 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -32,7 +32,7 @@ func BenchmarkEncodeStatusWire(b *testing.B) { LatestBlockTime: time.Unix(0, 1234), }, ValidatorInfo: ctypes.ValidatorInfo{ - PubKey: nodeKey.PubKey(), + PubKey: nodeKey.PubKey(), }, } b.StartTimer() diff --git a/cmd/tendermint/commands/show_node_id.go b/cmd/tendermint/commands/show_node_id.go index 1d94933ef..02ab1a9bb 100644 --- a/cmd/tendermint/commands/show_node_id.go +++ b/cmd/tendermint/commands/show_node_id.go @@ -6,7 +6,6 @@ import ( "github.com/spf13/cobra" "github.com/tendermint/tendermint/p2p" - ) // ShowNodeIDCmd dumps node's ID to the standard output. diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 6ead4bada..b354683b4 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "github.com/spf13/cobra" privval "github.com/tendermint/tendermint/types/priv_validator" diff --git a/consensus/reactor.go b/consensus/reactor.go index aabc46771..9535108c7 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -696,20 +696,37 @@ func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *cstype return true } } + // If there are POL prevotes to send... + if prs.Step <= cstypes.RoundStepPropose && prs.Round != -1 && prs.Round <= rs.Round && prs.ProposalPOLRound != -1 { + if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { + if ps.PickSendVote(polPrevotes) { + logger.Debug("Picked rs.Prevotes(prs.ProposalPOLRound) to send", + "round", prs.ProposalPOLRound) + return true + } + } + } // If there are prevotes to send... - if prs.Step <= cstypes.RoundStepPrevote && prs.Round != -1 && prs.Round <= rs.Round { + if prs.Step <= cstypes.RoundStepPrevoteWait && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round) return true } } // If there are precommits to send... - if prs.Step <= cstypes.RoundStepPrecommit && prs.Round != -1 && prs.Round <= rs.Round { + if prs.Step <= cstypes.RoundStepPrecommitWait && prs.Round != -1 && prs.Round <= rs.Round { if ps.PickSendVote(rs.Votes.Precommits(prs.Round)) { logger.Debug("Picked rs.Precommits(prs.Round) to send", "round", prs.Round) return true } } + // If there are prevotes to send...Needed because of validBlock mechanism + if prs.Round != -1 && prs.Round <= rs.Round { + if ps.PickSendVote(rs.Votes.Prevotes(prs.Round)) { + logger.Debug("Picked rs.Prevotes(prs.Round) to send", "round", prs.Round) + return true + } + } // If there are POLPrevotes to send... if prs.ProposalPOLRound != -1 { if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { @@ -720,6 +737,7 @@ func (conR *ConsensusReactor) gossipVotesForHeight(logger log.Logger, rs *cstype } } } + return false } diff --git a/consensus/state.go b/consensus/state.go index 4450c74e7..bee7efa2e 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1001,7 +1001,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { // check for a polka blockID, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() - // If we don't have a polka, we must precommit nil + // If we don't have a polka, we must precommit nil. if !ok { if cs.LockedBlock != nil { cs.Logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit while we're locked. Precommitting nil") @@ -1012,10 +1012,10 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { return } - // At this point +2/3 prevoted for a particular block or nil + // At this point +2/3 prevoted for a particular block or nil. cs.eventBus.PublishEventPolka(cs.RoundStateEvent()) - // the latest POLRound should be this round + // the latest POLRound should be this round. polRound, _ := cs.Votes.POLInfo() if polRound < round { cmn.PanicSanity(cmn.Fmt("This POLRound should be %v but got %", round, polRound)) @@ -1316,6 +1316,23 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v } // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) + + // Update Valid* if we can. + prevotes := cs.Votes.Prevotes(cs.Round) + blockID, hasTwoThirds := prevotes.TwoThirdsMajority() + if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) { + if cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.ValidRound = cs.Round + cs.ValidBlock = cs.ProposalBlock + cs.ValidBlockParts = cs.ProposalBlockParts + } + // TODO: In case there is +2/3 majority in Prevotes set for some + // block and cs.ProposalBlock contains different block, either + // proposer is faulty or voting power of faulty processes is more + // than 1/3. We should trigger in the future accountability + // procedure at this point. + } + if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() { // Move onto the next step cs.enterPrevote(height, cs.Round) @@ -1406,33 +1423,43 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, case types.VoteTypePrevote: prevotes := cs.Votes.Prevotes(vote.Round) cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort()) - blockID, ok := prevotes.TwoThirdsMajority() - // First, unlock if prevotes is a valid POL. - // >> lockRound < POLRound <= unlockOrChangeLockRound (see spec) - // NOTE: If (lockRound < POLRound) but !(POLRound <= unlockOrChangeLockRound), - // 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) { - if ok && !cs.LockedBlock.HashesTo(blockID.Hash) { + + // If +2/3 prevotes for a block or nil for *any* round: + if blockID, ok := prevotes.TwoThirdsMajority(); ok { + + // There was a polka! + // If we're locked but this is a recent polka, unlock. + // If it matches our ProposalBlock, update the ValidBlock + + // Unlock if `cs.LockedRound < vote.Round <= cs.Round` + // NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round + if (cs.LockedBlock != nil) && + (cs.LockedRound < vote.Round) && + (vote.Round <= cs.Round) && + !cs.LockedBlock.HashesTo(blockID.Hash) { + cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round) cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) } - } - // Update ValidBlock - if ok && !blockID.IsZero() && !cs.ValidBlock.HashesTo(blockID.Hash) && vote.Round > cs.ValidRound { - // update valid value - if cs.ProposalBlock.HashesTo(blockID.Hash) { + + // Update Valid* if we can. + // NOTE: our proposal block may be nil or not what received a polka.. + // TODO: we may want to still update the ValidBlock and obtain it via gossipping + if !blockID.IsZero() && + (cs.ValidRound < vote.Round) && + (vote.Round <= cs.Round) && + cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.ValidRound = vote.Round cs.ValidBlock = cs.ProposalBlock cs.ValidBlockParts = cs.ProposalBlockParts } - //TODO: We might want to update ValidBlock also in case we don't have that block yet, - // and obtain the required block using gossiping } + // If +2/3 prevotes for *anything* for this or future round: if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() { // Round-skip over to PrevoteWait or goto Precommit. cs.enterNewRound(height, vote.Round) // if the vote is ahead of us @@ -1448,6 +1475,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, cs.enterPrevote(height, cs.Round) } } + case types.VoteTypePrecommit: precommits := cs.Votes.Precommits(vote.Round) cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort()) diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index f69b8f39d..14da1f149 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -70,9 +70,9 @@ type RoundState struct { LockedRound int `json:"locked_round"` LockedBlock *types.Block `json:"locked_block"` LockedBlockParts *types.PartSet `json:"locked_block_parts"` - ValidRound int `json:"valid_round"` - ValidBlock *types.Block `json:"valid_block"` - ValidBlockParts *types.PartSet `json:"valid_block_parts"` + ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block. + ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. + ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above. Votes *HeightVoteSet `json:"votes"` CommitRound int `json:"commit_round"` // LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 diff --git a/rpc/client/helpers_test.go b/rpc/client/helpers_test.go index d5c31ddaf..8b843fcdb 100644 --- a/rpc/client/helpers_test.go +++ b/rpc/client/helpers_test.go @@ -32,7 +32,7 @@ func TestWaitForHeight(t *testing.T) { // now set current block height to 10 m.Call = mock.Call{ - Response: &ctypes.ResultStatus{SyncInfo: ctypes.SyncInfo{LatestBlockHeight: 10} }, + Response: &ctypes.ResultStatus{SyncInfo: ctypes.SyncInfo{LatestBlockHeight: 10}}, } // we will not wait for more than 10 blocks diff --git a/types/block.go b/types/block.go index 9cfef1f94..3004672c8 100644 --- a/types/block.go +++ b/types/block.go @@ -124,7 +124,7 @@ func (b *Block) MakePartSet(partSize int) *PartSet { } // HashesTo is a convenience function that checks if a block hashes to the given argument. -// A nil block never hashes to anything, and nothing hashes to a nil hash. +// Returns false if the block is nil or the hash is empty. func (b *Block) HashesTo(hash []byte) bool { if len(hash) == 0 { return false diff --git a/types/genesis_test.go b/types/genesis_test.go index 17ebf1cfc..bed4b90f5 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -1,9 +1,10 @@ package types import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/tendermint/go-crypto" - "testing" ) func TestGenesisBad(t *testing.T) { diff --git a/types/vote_set.go b/types/vote_set.go index 8908f86f2..a60d95daf 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -404,8 +404,8 @@ func (voteSet *VoteSet) HasAll() bool { return voteSet.sum == voteSet.valSet.TotalVotingPower() } -// Returns either a blockhash (or nil) that received +2/3 majority. -// If there exists no such majority, returns (nil, PartSetHeader{}, false). +// If there was a +2/3 majority for blockID, return blockID and true. +// Else, return the empty BlockID{} and false. func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) { if voteSet == nil { return BlockID{}, false