From 9e1794eaea69830a46442ecc3fa177a5a42a58cc Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 24 Jun 2015 14:04:40 -0700 Subject: [PATCH] Fixed tests --- consensus/height_vote_set.go | 14 ++-- consensus/reactor.go | 2 +- consensus/state.go | 107 ++++++++++++++++++------------- consensus/state_test.go | 10 ++- consensus/types/proposal_test.go | 13 ++-- consensus/vote_set.go | 30 ++------- rpc/test/helpers.go | 5 +- state/state_test.go | 15 ++--- types/block.go | 9 ++- 9 files changed, 102 insertions(+), 103 deletions(-) diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index 705d648e2..ebd94820a 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -42,7 +42,8 @@ func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { roundVoteSets: make(map[uint]RoundVoteSet), peerFastForward: make(map[string]uint), } - hvs.SetRound(0) + hvs.addRound(0) + hvs.round = 0 return hvs } @@ -85,14 +86,15 @@ func (hvs *HeightVoteSet) addRound(round uint) { } // Duplicate votes return added=false, err=nil. -func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peer string) (added bool, index uint, err error) { +// By convention, peerKey is "" if origin is self. +func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { - if _, ok := hvs.peerFastForward[peer]; !ok { + if _, ok := hvs.peerFastForward[peerKey]; !ok { hvs.addRound(vote.Round) - hvs.peerFastForward[peer] = vote.Round + hvs.peerFastForward[peerKey] = vote.Round } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! @@ -122,8 +124,8 @@ func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet { func (hvs *HeightVoteSet) POLRound() int { hvs.mtx.Lock() defer hvs.mtx.Unlock() - for r := hvs.round; r >= 0; r-- { - if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() { + for r := int(hvs.round); r >= 0; r-- { + if hvs.getVoteSet(uint(r), types.VoteTypePrevote).HasTwoThirdsMajority() { return int(r) } } diff --git a/consensus/reactor.go b/consensus/reactor.go index 7ef9ec02d..c8cce2d79 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -187,7 +187,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } VOTE_PASS: address, _ := rs.Validators.GetByIndex(msg.ValidatorIndex) - added, index, err := conR.conS.AddVote(address, vote) + added, index, err := conR.conS.AddVote(address, vote, peer.Key) if err != nil { // If conflicting sig, broadcast evidence tx for slashing. Else punish peer. if errDupe, ok := err.(*types.ErrVoteConflictingSignature); ok { diff --git a/consensus/state.go b/consensus/state.go index 078c3e078..4fd5b183e 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -252,6 +252,8 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto newStepCh: make(chan *RoundState, 10), } cs.updateToState(state, true) + // Don't call scheduleRound0 yet. + // We do that upon Start(). cs.maybeRebond() cs.reconstructLastCommit(state) return cs @@ -268,7 +270,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { for idx, precommit := range seenValidation.Precommits { precommitVote := &types.Vote{ Height: state.LastBlockHeight, - Round: seenValidation.Round, + Round: seenValidation.Round(), Type: types.VoteTypePrecommit, BlockHash: state.LastBlockHash, BlockParts: state.LastBlockParts, @@ -309,11 +311,12 @@ func (cs *ConsensusState) NewStepCh() chan *RoundState { func (cs *ConsensusState) Start() { if atomic.CompareAndSwapUint32(&cs.started, 0, 1) { log.Info("Starting ConsensusState") - cs.scheduleRound0() + cs.scheduleRound0(cs.Height) } } func (cs *ConsensusState) scheduleRound0(height uint) { + log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) sleepDuration := cs.StartTime.Sub(time.Now()) go func() { if sleepDuration > 0 { @@ -423,7 +426,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { func (cs *ConsensusState) EnterNewRound(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round >= round { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != RoundStepNewHeight) { log.Debug(Fmt("EnterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -432,8 +435,9 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) { } // Increment validators if necessary + validators := cs.Validators if cs.Round < round { - validators := cs.Validators.Copy() + validators = validators.Copy() validators.IncrementAccum(round - cs.Round) } @@ -517,18 +521,23 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { } else { log.Warn("EnterPropose: Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err) } + } func (cs *ConsensusState) isProposalComplete() bool { if cs.Proposal == nil || cs.ProposalBlock == nil { return false } - return cs.Votes.Prevote(cs.Proposal.POLRound).HasTwoThirdsMajority() + if cs.Proposal.POLRound < 0 { + return true + } else { + return cs.Votes.Prevotes(uint(cs.Proposal.POLRound)).HasTwoThirdsMajority() + } } // Create the next block to propose and return it. // NOTE: make it side-effect free for clarity. -func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { +func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts *types.PartSet) { var validation *types.Validation if cs.Height == 1 { // We're creating a proposal for the first block. @@ -554,7 +563,7 @@ func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { LastBlockParts: cs.state.LastBlockParts, StateHash: nil, // Will set afterwards. }, - Validation: validation, + LastValidation: validation, Data: &types.Data{ Txs: txs, }, @@ -645,9 +654,9 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { cs.Step = RoundStepPrevoteWait cs.newStepCh <- cs.getRoundState() - // After `timeoutPrevote`, EnterPrecommit() + // After `timeoutPrevote0+timeoutPrevoteDelta*round`, EnterPrecommit() go func() { - time.Sleep(timeoutPrevote) + time.Sleep(timeoutPrevote0 + timeoutPrevote0*time.Duration(round)) cs.EnterPrecommit(height, round) }() } @@ -734,7 +743,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { cs.ProposalBlock = nil cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader) } - cs.signAddVote(types.VoteTypePrecommit, nil, PartSetHeader{}) + cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) return } @@ -755,9 +764,9 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { cs.Step = RoundStepPrecommitWait cs.newStepCh <- cs.getRoundState() - // After `timeoutPrecommit`, EnterNewRound() + // After `timeoutPrecommit0+timeoutPrecommitDelta*round`, EnterNewRound() go func() { - time.Sleep(timeoutPrecommit) + time.Sleep(timeoutPrecommit0 + timeoutPrecommitDelta*time.Duration(round)) // If we have +2/3 of precommits for a particular block (or nil), // we already entered commit (or the next round). // So just try to transition to the next round, @@ -782,11 +791,11 @@ func (cs *ConsensusState) EnterCommit(height uint) { cs.newStepCh <- cs.getRoundState() // Maybe finalize immediately. - cs.TryFinalizeCommit(height) + cs.tryFinalizeCommit(height) }() // SANITY CHECK - hash, partsHeader, ok := cs.Precommits.TwoThirdsMajority() + hash, partsHeader, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() if !ok { panic("RunActionCommit() expects +2/3 precommits") } @@ -815,17 +824,25 @@ func (cs *ConsensusState) EnterCommit(height uint) { } else { // We just need to keep waiting. } - } else { - // We have the block, so sign a Commit-vote. - cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts) } } // If we have the block AND +2/3 commits for it, finalize. -func (cs *ConsensusState) TryFinalizeCommit(height uint) { - if cs.ProposalBlock.HashesTo(hash) && cs.Commits.HasTwoThirdsMajority() { - go cs.FinalizeCommit(height) +func (cs *ConsensusState) tryFinalizeCommit(height uint) { + // SANITY CHECK + if cs.Height != height { + panic(Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) } + // END SANITY CHECK + + hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() + if !ok || len(hash) == 0 { + return + } + if !cs.ProposalBlock.HashesTo(hash) { + return + } + go cs.FinalizeCommit(height) } // Increment height and goto RoundStepNewHeight @@ -838,7 +855,7 @@ func (cs *ConsensusState) FinalizeCommit(height uint) { return } - hash, header, ok := cs.Commits.TwoThirdsMajority() + hash, header, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() // SANITY CHECK if !ok { @@ -857,26 +874,19 @@ func (cs *ConsensusState) FinalizeCommit(height uint) { log.Debug(Fmt("Finalizing commit of block: %v", cs.ProposalBlock)) // We have the block, so stage/save/commit-vote. - cs.saveBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) - cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) - // Fire off event - go func() { - cs.evsw.FireEvent(types.EventStringNewBlock(), cs.ProposalBlock) - cs.evc.Flush() - }(cs.ProposalBlock) - + cs.saveBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Votes.Precommits(cs.Round)) // Increment height. cs.updateToState(cs.stagedState, true) - + // cs.StartTime is already set. + // Schedule Round0 to start soon. + go cs.scheduleRound0(height + 1) // If we're unbonded, broadcast RebondTx. cs.maybeRebond() // By here, // * cs.Height has been increment to height+1 // * cs.Step is now RoundStepNewHeight - // * cs.StartTime is set to when we should start round0. - // Start round 0 when cs.StartTime. - go cs.scheduleRound0(height) + // * cs.StartTime is set to when we will start round0. return } @@ -903,7 +913,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. if proposal.POLRound != -1 && - (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { + (proposal.POLRound < 0 || proposal.Round <= uint(proposal.POLRound)) { return ErrInvalidProposalPOLRound } @@ -913,7 +923,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } cs.Proposal = proposal - cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockParts) + cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) return nil } @@ -944,23 +954,23 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty if cs.Step == RoundStepPropose && cs.isProposalComplete() { go cs.EnterPrevote(height, round) } else if cs.Step == RoundStepCommit { - cs.TryFinalizeCommit(height) + cs.tryFinalizeCommit(height) } return true, err } return true, nil } -func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() - return cs.addVote(address, vote) + return cs.addVote(address, vote, peerKey) } //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { // A precommit for the previous height? if vote.Height+1 == cs.Height && vote.Type == types.VoteTypePrecommit { added, index, err = cs.LastCommit.AddByAddress(address, vote) @@ -972,7 +982,8 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, // A prevote/precommit for this height? if vote.Height == cs.Height { - added, index, err = cs.Votes.AddByAddress(address, vote) + height := cs.Height + added, index, err = cs.Votes.AddByAddress(address, vote, peerKey) if added { switch vote.Type { case types.VoteTypePrevote: @@ -995,7 +1006,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, cs.EnterPrevoteWait(height, cs.Round) }() } - } else if cs.Proposal != nil && cs.Proposal.POLRound == vote.Round { + } else if cs.Proposal != nil && cs.Proposal.POLRound >= 0 && uint(cs.Proposal.POLRound) == vote.Round { if cs.isProposalComplete() { go cs.EnterPrevote(height, cs.Round) } @@ -1017,7 +1028,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, // If hash is block, goto Commit go func() { cs.EnterNewRound(height, vote.Round) - cs.EnterCommit(height, vote.Round) + cs.EnterCommit(height) }() } } else if cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { @@ -1035,7 +1046,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, go cs.EnterNewRound(height, cs.Round+1) } else { // If hash is block, goto Commit - go cs.EnterCommit(height, cs.Round) + go cs.EnterCommit(height) } } else if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { // Goto PrecommitWait @@ -1098,8 +1109,8 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.Part } err := cs.privValidator.SignVote(cs.state.ChainID, vote) if err == nil { - log.Info("Signed and added vote", "height", cs.Height, "round", cs.Round, "vote", vote) - cs.addVote(cs.privValidator.Address, vote) + _, _, err := cs.addVote(cs.privValidator.Address, vote, "") + log.Info("Signed and added vote", "height", cs.Height, "round", cs.Round, "vote", vote, "error", err) return vote } else { log.Warn("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "error", err) @@ -1127,6 +1138,12 @@ func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSe // Update mempool. cs.mempoolReactor.Mempool.ResetForBlockAndState(block, cs.stagedState) + // Fire off event + go func(block *types.Block) { + cs.evsw.FireEvent(types.EventStringNewBlock(), block) + cs.evc.Flush() + }(block) + } // implements events.Eventable diff --git a/consensus/state_test.go b/consensus/state_test.go index 48e8bb503..599d3c399 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1,28 +1,26 @@ package consensus import ( - "bytes" "testing" _ "github.com/tendermint/tendermint/config/tendermint_test" - "github.com/tendermint/tendermint/types" ) -func TestRunActionProposeNoPrivValidator(t *testing.T) { +func TestEnterProposeNoPrivValidator(t *testing.T) { cs, _ := randConsensusState() - cs.RunActionPropose(1, 0) + cs.EnterPropose(1, 0) rs := cs.GetRoundState() if rs.Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") } } -func TestRunActionPropose(t *testing.T) { +func TestEnterPropose(t *testing.T) { cs, privValidators := randConsensusState() val0 := privValidators[0] cs.SetPrivValidator(val0) - cs.RunActionPropose(1, 0) + cs.EnterPropose(1, 0) rs := cs.GetRoundState() // Check that Proposal, ProposalBlock, ProposalBlockParts are set. diff --git a/consensus/types/proposal_test.go b/consensus/types/proposal_test.go index 5cc4a5f38..9e68789c9 100644 --- a/consensus/types/proposal_test.go +++ b/consensus/types/proposal_test.go @@ -11,15 +11,16 @@ import ( func TestProposalSignable(t *testing.T) { proposal := &Proposal{ - Height: 12345, - Round: 23456, - BlockParts: types.PartSetHeader{111, []byte("blockparts")}, - POLParts: types.PartSetHeader{222, []byte("polparts")}, - Signature: nil, + Height: 12345, + Round: 23456, + BlockPartsHeader: types.PartSetHeader{111, []byte("blockparts")}, + POLRound: -1, + Signature: nil, } signBytes := account.SignBytes(config.GetString("chain_id"), proposal) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_parts":{"hash":"706F6C7061727473","total":222},"round":23456}}`, + + expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_round":-1,"round":23456}}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) diff --git a/consensus/vote_set.go b/consensus/vote_set.go index f287b354f..2227cfe98 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -199,7 +199,7 @@ func (voteSet *VoteSet) HasTwoThirdsAny() bool { } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() - return voteSet.totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3 + return voteSet.totalVotes > voteSet.valSet.TotalVotingPower()*2/3 } // Returns either a blockhash (or nil) that received +2/3 majority. @@ -215,6 +215,9 @@ func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHea } func (voteSet *VoteSet) String() string { + if voteSet == nil { + return "nil-VoteSet" + } return voteSet.StringIndented("") } @@ -260,7 +263,7 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { if len(voteSet.maj23Hash) == 0 { panic("Cannot MakeValidation() unless a blockhash has +2/3") } - precommits := make([]types.Precommit, voteSet.valSet.Size()) + precommits := make([]*types.Vote, voteSet.valSet.Size()) voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { vote := voteSet.votes[valIndex] if vote == nil { @@ -272,31 +275,10 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { if !vote.BlockParts.Equals(voteSet.maj23Parts) { return false } - precommits[valIndex] = types.Precommit{val.Address, vote.Signature} + precommits[valIndex] = vote return false }) return &types.Validation{ - Round: voteSet.round, Precommits: precommits, } } - -// XXX -func VoteSetFromValidation(validation *types.Validation) *VoteSet { - lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) - seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) - for idx, precommit := range seenValidation.Precommits { - precommitVote := &types.Vote{ - Height: state.LastBlockHeight, - Round: seenValidation.Round, - Type: types.VoteTypePrecommit, - BlockHash: state.LastBlockHash, - BlockParts: state.LastBlockParts, - Signature: precommit.Signature, - } - added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) - if !added || err != nil { - panic(Fmt("Failed to reconstruct LastPrecommits: %v", err)) - } - } -} diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 56e338820..e794ef296 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -4,11 +4,9 @@ import ( "bytes" "strconv" "testing" - "time" "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/consensus" nm "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -79,8 +77,7 @@ func init() { priv.SetFile(config.GetString("priv_validator_file")) priv.Save() - consensus.RoundDuration0 = 2 * time.Second - consensus.RoundDurationDelta = 1 * time.Second + // TODO: change consensus/state.go timeouts to be shorter // start a node ready := make(chan struct{}) diff --git a/state/state_test.go b/state/state_test.go index 9e5219ddc..6a221977e 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -72,6 +72,9 @@ func TestCopyState(t *testing.T) { } func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []types.Tx) *types.Block { + if validation == nil { + validation = &types.Validation{} + } block := &types.Block{ Header: &types.Header{ ChainID: state.ChainID, @@ -83,7 +86,7 @@ func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []t LastBlockParts: state.LastBlockParts, StateHash: nil, }, - Validation: validation, + LastValidation: validation, Data: &types.Data{ Txs: txs, }, @@ -628,13 +631,9 @@ func TestAddValidator(t *testing.T) { privValidators[0].SignVote(s0.ChainID, precommit0) block1 := makeBlock(t, s0, - types.Validation{ - Round: 0, - Precommits: []types.Precommit{ - types.Precommit{ - Address: privValidators[0].Address, - Signature: precommit0.Signature, - }, + &types.Validation{ + Precommits: []*types.Vote{ + precommit0, }, }, nil, ) diff --git a/types/block.go b/types/block.go index 59e26a807..b149c44e8 100644 --- a/types/block.go +++ b/types/block.go @@ -208,6 +208,10 @@ func (v *Validation) ValidateBasic() error { } height, round := v.Height(), v.Round() for _, precommit := range v.Precommits { + // It's OK for precommits to be missing. + if precommit == nil { + continue + } // Ensure that all votes are precommits if precommit.Type != VoteTypePrecommit { return fmt.Errorf("Invalid validation vote. Expected precommit, got %v", @@ -229,10 +233,9 @@ func (v *Validation) ValidateBasic() error { func (v *Validation) Hash() []byte { if v.hash == nil { - bs := make([]interface{}, 1+len(v.Precommits)) - bs[0] = v.Round + bs := make([]interface{}, len(v.Precommits)) for i, precommit := range v.Precommits { - bs[1+i] = precommit + bs[i] = precommit } v.hash = merkle.SimpleHashFromBinaries(bs) }