From 9dea9539b4acb1d2e8d0af82f9412fd9ee6b5813 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 13 Dec 2015 14:56:05 -0500 Subject: [PATCH] fix consensus tests --- consensus/common_test.go | 44 ++- consensus/state.go | 4 +- consensus/state_test.go | 830 ++++++++++++++++----------------------- types/block.go | 8 +- 4 files changed, 386 insertions(+), 500 deletions(-) diff --git a/consensus/common_test.go b/consensus/common_test.go index f31f69ae1..40609c6e9 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -203,6 +203,18 @@ func waitFor(t *testing.T, cs *ConsensusState, height int, round int, step Round } } +func incrementHeight(vss ...*validatorStub) { + for _, vs := range vss { + vs.Height += 1 + } +} + +func incrementRound(vss ...*validatorStub) { + for _, vs := range vss { + vs.Round += 1 + } +} + func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *validatorStub, blockHash []byte) { prevotes := cs.Votes.Prevotes(round) var vote *types.Vote @@ -220,15 +232,14 @@ func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *valid } } -func incrementHeight(vss ...*validatorStub) { - for _, vs := range vss { - vs.Height += 1 +func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorStub, blockHash []byte) { + votes := cs.LastCommit + var vote *types.Vote + if vote = votes.GetByAddress(privVal.Address); vote == nil { + panic("Failed to find precommit from validator") } -} - -func incrementRound(vss ...*validatorStub) { - for _, vs := range vss { - vs.Round += 1 + if !bytes.Equal(vote.BlockHash, blockHash) { + panic(fmt.Sprintf("Expected precommit to be for %X, got %X", blockHash, vote.BlockHash)) } } @@ -332,6 +343,23 @@ func subscribeToEvent(cs *ConsensusState, eventID string) chan interface{} { return ch } +func subscribeToVoter(cs *ConsensusState, addr []byte) chan interface{} { + + voteCh0 := subscribeToEvent(cs, types.EventStringVote()) + voteCh := make(chan interface{}) + go func() { + for { + v := <-voteCh0 + vote := v.(*types.EventDataVote) + // we only fire for our own votes + if bytes.Equal(addr, vote.Address) { + voteCh <- v + } + } + }() + return voteCh +} + func randGenesisState(numValidators int, randPower bool, minPower int64) (*sm.State, []*types.PrivValidator) { db := dbm.NewMemDB() genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) diff --git a/consensus/state.go b/consensus/state.go index d83eae446..4f4723615 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -332,7 +332,7 @@ func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *t cs.SetProposal(proposal, peerKey) for i := 0; i < parts.Total(); i++ { part := parts.GetPart(i) - cs.AddProposalBlockPart(cs.Height, cs.Round, part, peerKey) + cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerKey) } return nil // TODO errors } @@ -1219,7 +1219,7 @@ func (cs *ConsensusState) addProposalBlockPart(height int, part *types.Part) (ad var n int var err error cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), types.MaxBlockSize, &n, &err).(*types.Block) - log.Info("Received complete proposal", "hash", cs.ProposalBlock.Hash()) + log.Info("Received complete proposal", "hash", cs.ProposalBlock.Hash(), "round", cs.Proposal.Round) if cs.Step == RoundStepPropose && cs.isProposalComplete() { // Move onto the next step cs.EnterPrevote(height, cs.Round) diff --git a/consensus/state_test.go b/consensus/state_test.go index 682ade2fa..13619965a 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -8,7 +8,7 @@ import ( _ "github.com/tendermint/tendermint/config/tendermint_test" //"github.com/tendermint/tendermint/events" - "github.com/tendermint/tendermint/events" + // "github.com/tendermint/tendermint/events" "github.com/tendermint/tendermint/types" ) @@ -235,18 +235,26 @@ func TestFullRound1(t *testing.T) { height, round := cs.Height, cs.Round voteCh := subscribeToEvent(cs, types.EventStringVote()) + propCh := subscribeToEvent(cs, types.EventStringCompleteProposal()) + newRoundCh := subscribeToEvent(cs, types.EventStringNewRound()) - cs.EnterNewRound(height, round) - cs.startRoutines(5) + startTestRound(cs, height, round) + + <-newRoundCh + + // grab proposal + re := <-propCh + propBlockHash := re.(*types.EventDataRoundState).ProposalBlock.Hash() <-voteCh // wait for prevote - <-voteCh // wait for precommit + validatePrevote(t, cs, round, vss[0], propBlockHash) - propBlockHash := cs.GetRoundState().ProposalBlock.Hash() + <-voteCh // wait for precommit - // the proposed block should be prevoted, precommitted, and locked - validatePrevoteAndPrecommit(t, cs, round, round, vss[0], propBlockHash, propBlockHash) + // we're going to roll right into new height + <-newRoundCh + validateLastPrecommit(t, cs, vss[0], propBlockHash) } // nil is proposed, so prevote and precommit nil @@ -313,38 +321,34 @@ func TestFullRound2(t *testing.T) { func TestLockNoPOL(t *testing.T) { cs1, vss := simpleConsensusState(2) cs2 := vss[1] - cs1.newStepCh = make(chan *RoundState) // so it blocks height := cs1.Height - timeoutChan := make(chan struct{}) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { - timeoutChan <- struct{}{} - }) - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- struct{}{} - }) - cs1.SetFireable(evsw) + timeoutProposeCh := subscribeToEvent(cs1, types.EventStringTimeoutPropose()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + voteCh := subscribeToEvent(cs1, types.EventStringVote()) + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) /* Round1 (cs1, B) // B B // B B2 */ // start round and wait for prevote - go cs1.EnterNewRound(height, 0) - waitFor(t, cs1, height, 0, RoundStepPrevote) + cs1.EnterNewRound(height, 0) + cs1.startRoutines(0) + + re := <-proposalCh + rs := re.(*types.EventDataRoundState) + theBlockHash := rs.ProposalBlock.Hash() + + <-voteCh // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from cs2: signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + <-voteCh // prevote - cs1.mtx.Lock() // XXX: sigh - theBlockHash := cs1.ProposalBlock.Hash() - cs1.mtx.Unlock() - - // wait to finish precommit - waitFor(t, cs1, height, 0, RoundStepPrecommit) + <-voteCh // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) @@ -352,16 +356,20 @@ func TestLockNoPOL(t *testing.T) { // we should now be stuck in limbo forever, waiting for more precommits // lets add one for a different block // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round - hash := cs1.ProposalBlock.Hash() + hash := make([]byte, len(theBlockHash)) + copy(hash, theBlockHash) hash[0] = byte((hash[0] + 1) % 255) - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header()) + <-voteCh // precommit // (note we're entering precommit for a second time this round) // but with invalid args. then we EnterPrecommitWait, and the timeout to new round - waitFor(t, cs1, height, 0, RoundStepPrecommitWait) - <-timeoutChan + <-timeoutWaitCh + + /// - log.Info("#### ONTO ROUND 2") + <-newRoundCh + log.Notice("#### ONTO ROUND 1") /* Round2 (cs1, B) // B B2 */ @@ -369,27 +377,28 @@ func TestLockNoPOL(t *testing.T) { incrementRound(cs2) // now we're on a new round and not the proposer, so wait for timeout - waitFor(t, cs1, height, 1, RoundStepPropose) - <-timeoutChan + re = <-timeoutProposeCh + rs = re.(*types.EventDataRoundState) - if cs1.ProposalBlock != nil { + if rs.ProposalBlock != nil { t.Fatal("Expected proposal block to be nil") } // wait to finish prevote - waitFor(t, cs1, height, 1, RoundStepPrevote) + <-voteCh // we should have prevoted our locked block - validatePrevote(t, cs1, 1, vss[0], cs1.LockedBlock.Hash()) + validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator - signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header()) + <-voteCh // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit - waitFor(t, cs1, height, 1, RoundStepPrevoteWait) - <-timeoutChan - waitFor(t, cs1, height, 1, RoundStepPrecommit) + <-timeoutWaitCh + + <-voteCh // precommit // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal @@ -397,43 +406,46 @@ func TestLockNoPOL(t *testing.T) { // add conflicting precommit from cs2 // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header()) + <-voteCh // (note we're entering precommit for a second time this round, but with invalid args // then we EnterPrecommitWait and timeout into NewRound - waitFor(t, cs1, height, 1, RoundStepPrecommitWait) - <-timeoutChan + <-timeoutWaitCh - log.Info("#### ONTO ROUND 3") + <-newRoundCh + log.Notice("#### ONTO ROUND 2") /* Round3 (cs2, _) // B, B2 */ incrementRound(cs2) - waitFor(t, cs1, height, 2, RoundStepPropose) + re = <-proposalCh + rs = re.(*types.EventDataRoundState) // now we're on a new round and are the proposer - if cs1.ProposalBlock != cs1.LockedBlock { - t.Fatalf("Expected proposal block to be locked block. Got %v, Expected %v", cs1.ProposalBlock, cs1.LockedBlock) + if !bytes.Equal(rs.ProposalBlock.Hash(), rs.LockedBlock.Hash()) { + t.Fatalf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock) } - // go to prevote, prevote for locked block - waitFor(t, cs1, height, 2, RoundStepPrevote) + <-voteCh // prevote - validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) + // TODO: is the round right?! + validatePrevote(t, cs1, 0, vss[0], rs.LockedBlock.Hash()) // TODO: quick fastforward to new round, set proposer - signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header()) + <-voteCh - waitFor(t, cs1, height, 2, RoundStepPrevoteWait) - <-timeoutChan - waitFor(t, cs1, height, 2, RoundStepPrecommit) + <-timeoutWaitCh // prevote wait + <-voteCh // precommit - validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) // NOTE: conflicting precommits at same height + validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header()) // NOTE: conflicting precommits at same height + <-voteCh - waitFor(t, cs1, height, 2, RoundStepPrecommitWait) + <-timeoutWaitCh // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) @@ -443,66 +455,45 @@ func TestLockNoPOL(t *testing.T) { incrementRound(cs2) - <-timeoutChan - - log.Info("#### ONTO ROUND 4") + <-newRoundCh + log.Notice("#### ONTO ROUND 3") /* Round4 (cs2, C) // B C // B C */ // now we're on a new round and not the proposer // so set the proposal block - cs1.mtx.Lock() - cs1.Proposal, cs1.ProposalBlock = prop, propBlock - cs1.mtx.Unlock() - - // wait for the proposal go ahead - waitFor(t, cs1, height, 3, RoundStepPropose) - - //log.Debug("waiting for timeout") - // and wait for timeout - // go func() { <-timeoutChan }() + cs1.SetProposalAndBlock(prop, propBlock, propBlock.MakePartSet(), "") - log.Debug("waiting for prevote") - // go to prevote, prevote for locked block (not proposal) - waitFor(t, cs1, height, 3, RoundStepPrevote) + <-voteCh // prevote + // prevote for locked block (not proposal) validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) + <-voteCh - waitFor(t, cs1, height, 3, RoundStepPrevoteWait) - <-timeoutChan - waitFor(t, cs1, height, 3, RoundStepPrecommit) + <-timeoutWaitCh + <-voteCh validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header()) // NOTE: conflicting precommits at same height + <-voteCh } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestLockPOLRelock(t *testing.T) { cs1, vss := simpleConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] - cs1.newStepCh = make(chan *RoundState) // so it blocks - - timeoutChan := make(chan *types.EventDataRoundState) - voteChan := make(chan *types.EventDataVote) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { - vote := data.(*types.EventDataVote) - // we only fire for our own votes - if bytes.Equal(cs1.privValidator.Address, vote.Address) { - voteChan <- vote - } - }) - cs1.SetFireable(evsw) + + timeoutProposeCh := subscribeToEvent(cs1, types.EventStringTimeoutPropose()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + voteCh := subscribeToEvent(cs1, types.EventStringVote()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + newBlockCh := subscribeToEvent(cs1, types.EventStringNewBlock()) + + log.Debug("cs2 last round", "lr", cs2.PrivValidator.LastRound) // everything done from perspective of cs1 @@ -513,58 +504,42 @@ func TestLockPOLRelock(t *testing.T) { */ // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() - - theBlockHash := cs1.ProposalBlock.Hash() - - // wait to finish precommit after prevotes done - // we do this in a go routine with another channel since otherwise - // we may get deadlock with EnterPrecommit waiting to send on newStepCh and the final - // signAddVoteToFrom waiting for the cs.mtx.Lock - donePrecommit := make(chan struct{}) - go func() { - <-voteChan - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() + startTestRound(cs1, cs1.Height, 0) + + <-newRoundCh + re := <-proposalCh + rs := re.(*types.EventDataRoundState) + theBlockHash := rs.ProposalBlock.Hash() + + <-voteCh // prevote + signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) - <-donePrecommit + _, _, _ = <-voteCh, <-voteCh, <-voteCh // prevotes + <-voteCh // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) - donePrecommitWait := make(chan struct{}) - go func() { - // (note we're entering precommit for a second time this round) - // but with invalid args. then we EnterPrecommitWait, twice (?) - <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) - <-donePrecommitWait - - // before we time out into new round, set next proposer - // and next proposal block - _, v1 := cs1.Validators.GetByAddress(vss[0].Address) - v1.VotingPower = 1 - if updated := cs1.Validators.Update(v1); !updated { - t.Fatal("failed to update validator") - } + _, _, _ = <-voteCh, <-voteCh, <-voteCh // precommits + // before we timeout to the new round set the new proposal prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) + propBlockParts := propBlock.MakePartSet() + propBlockHash := propBlock.Hash() incrementRound(cs2, cs3, cs4) // timeout to new round - te := <-timeoutChan - if te.Step != RoundStepPrecommitWait.String() { - t.Fatalf("expected to timeout of precommit into new round. got %v", te.Step) - } + <-timeoutWaitCh - log.Info("### ONTO ROUND 2") + //XXX: this isnt gauranteed to get there before the timeoutPropose ... + cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer") + + <-newRoundCh + log.Notice("### ONTO ROUND 1") /* Round2 (cs2, C) // B C C C // C C C _) @@ -573,53 +548,44 @@ func TestLockPOLRelock(t *testing.T) { */ // now we're on a new round and not the proposer - // so set the proposal block - cs1.mtx.Lock() - propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() - cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts - cs1.mtx.Unlock() - <-cs1.NewStepCh() + // but we should receive the proposal + select { + case <-proposalCh: + case <-timeoutProposeCh: + <-proposalCh + } // go to prevote, prevote for locked block (not proposal), move on - _, _ = <-voteChan, <-cs1.NewStepCh() + <-voteCh validatePrevote(t, cs1, 0, vss[0], theBlockHash) - donePrecommit = make(chan struct{}) - go func() { - // we need this go routine because if we go into PrevoteWait it has to pull on newStepCh - // before the final vote will get added (because it holds the mutex). - select { - case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit - <-voteChan - case <-voteChan: // we went straight to Precommit - } - donePrecommit <- struct{}{} - }() // now lets add prevotes from everyone else for the new block signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) - <-donePrecommit + _, _, _ = <-voteCh, <-voteCh, <-voteCh // prevotes + + // now either we go to PrevoteWait or Precommit + select { + case <-timeoutWaitCh: // we're in PrevoteWait, go to Precommit + <-voteCh + case <-voteCh: // we went straight to Precommit + } // we should have unlocked and locked on the new block validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) - donePrecommitWait = make(chan struct{}) - go func() { - // (note we're entering precommit for a second time this round) - // but with invalid args. then we EnterPrecommitWait, - <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3) - <-donePrecommitWait + _, _ = <-voteCh, <-voteCh - <-cs1.NewStepCh() - rs := <-cs1.NewStepCh() + be := <-newBlockCh + b := be.(types.EventDataNewBlock) + re = <-newRoundCh + rs = re.(*types.EventDataRoundState) if rs.Height != 2 { t.Fatal("Expected height to increment") } - if hash, _, ok := rs.LastCommit.TwoThirdsMajority(); !ok || !bytes.Equal(hash, propBlockHash) { - t.Fatal("Expected block to get committed") + if !bytes.Equal(b.Block.Hash(), propBlockHash) { + t.Fatal("Expected new block to be proposal block") } } @@ -627,26 +593,13 @@ func TestLockPOLRelock(t *testing.T) { func TestLockPOLUnlock(t *testing.T) { cs1, vss := simpleConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] - cs1.newStepCh = make(chan *RoundState) // so it blocks - - timeoutChan := make(chan *types.EventDataRoundState) - voteChan := make(chan *types.EventDataVote) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { - vote := data.(*types.EventDataVote) - // we only fire for our own votes - if bytes.Equal(cs1.privValidator.Address, vote.Address) { - voteChan <- vote - } - }) - cs1.SetFireable(evsw) + + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutProposeCh := subscribeToEvent(cs1, types.EventStringTimeoutPropose()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + unlockCh := subscribeToEvent(cs1, types.EventStringUnlock()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // everything done from perspective of cs1 @@ -657,49 +610,41 @@ func TestLockPOLUnlock(t *testing.T) { */ // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + startTestRound(cs1, cs1.Height, 0) + <-newRoundCh + re := <-proposalCh + rs := re.(*types.EventDataRoundState) + theBlockHash := rs.ProposalBlock.Hash() - theBlockHash := cs1.ProposalBlock.Hash() + <-voteCh // prevote - donePrecommit := make(chan struct{}) - go func() { - <-voteChan - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) - <-donePrecommit + + <-voteCh //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) - donePrecommitWait := make(chan struct{}) - go func() { - <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() // add precommits from the rest signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) - <-donePrecommitWait - - // before we time out into new round, set next proposer - // and next proposal block - _, v1 := cs1.Validators.GetByAddress(vss[0].Address) - v1.VotingPower = 1 - if updated := cs1.Validators.Update(v1); !updated { - t.Fatal("failed to update validator") - } + // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) + propBlockParts := propBlock.MakePartSet() incrementRound(cs2, cs3, cs4) // timeout to new round - <-timeoutChan + re = <-timeoutWaitCh + rs = re.(*types.EventDataRoundState) + lockedBlockHash := rs.LockedBlock.Hash() + + //XXX: this isnt gauranteed to get there before the timeoutPropose ... + cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer") - log.Info("#### ONTO ROUND 2") + <-newRoundCh + log.Notice("#### ONTO ROUND 1") /* Round2 (cs2, C) // B nil nil nil // nil nil nil _ @@ -707,43 +652,29 @@ func TestLockPOLUnlock(t *testing.T) { */ // now we're on a new round and not the proposer, - // so set the proposal block - cs1.mtx.Lock() - cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() - lockedBlockHash := cs1.LockedBlock.Hash() - cs1.mtx.Unlock() - <-cs1.NewStepCh() + // but we should receive the proposal + select { + case <-proposalCh: + case <-timeoutProposeCh: + <-proposalCh + } // go to prevote, prevote for locked block (not proposal) - _, _ = <-voteChan, <-cs1.NewStepCh() + <-voteCh validatePrevote(t, cs1, 0, vss[0], lockedBlockHash) - - donePrecommit = make(chan struct{}) - go func() { - select { - case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit - <-voteChan - case <-voteChan: // we went straight to Precommit - } - donePrecommit <- struct{}{} - }() - // now lets add prevotes from everyone else for the new block + // now lets add prevotes from everyone else for nil (a polka!) signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) - <-donePrecommit - // we should have unlocked - // NOTE: we don't lock on nil, so LockedRound is still 0 + // the polka makes us unlock and precommit nil + <-unlockCh + <-voteCh // precommit + + // we should have unlocked and committed nil + // NOTE: since we don't relock on nil, the lock round is 0 validatePrecommit(t, cs1, 1, 0, vss[0], nil, nil) - donePrecommitWait = make(chan struct{}) - go func() { - // the votes will bring us to new round right away - // we should timeout of it - _, _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh(), <-timeoutChan - donePrecommitWait <- struct{}{} - }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) - <-donePrecommitWait + <-newRoundCh } // 4 vals @@ -753,45 +684,35 @@ func TestLockPOLUnlock(t *testing.T) { func TestLockPOLSafety1(t *testing.T) { cs1, vss := simpleConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] - cs1.newStepCh = make(chan *RoundState) // so it blocks - - timeoutChan := make(chan *types.EventDataRoundState) - voteChan := make(chan *types.EventDataVote) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { - vote := data.(*types.EventDataVote) - // we only fire for our own votes - if bytes.Equal(cs1.privValidator.Address, vote.Address) { - voteChan <- vote - } - }) - cs1.SetFireable(evsw) + + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutProposeCh := subscribeToEvent(cs1, types.EventStringTimeoutPropose()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + startTestRound(cs1, cs1.Height, 0) + <-newRoundCh + re := <-proposalCh + rs := re.(*types.EventDataRoundState) + propBlock := rs.ProposalBlock - propBlock := cs1.ProposalBlock + <-voteCh // prevote - validatePrevote(t, cs1, 0, vss[0], cs1.ProposalBlock.Hash()) + validatePrevote(t, cs1, 0, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) // before we time out into new round, set next proposer // and next proposal block - _, v1 := cs1.Validators.GetByAddress(vss[0].Address) - v1.VotingPower = 1 - if updated := cs1.Validators.Update(v1); !updated { - t.Fatal("failed to update validator") - } + /* + _, v1 := cs1.Validators.GetByAddress(vss[0].Address) + v1.VotingPower = 1 + if updated := cs1.Validators.Update(v1); !updated { + t.Fatal("failed to update validator") + }*/ log.Warn("old prop", "hash", fmt.Sprintf("%X", propBlock.Hash())) @@ -799,73 +720,65 @@ func TestLockPOLSafety1(t *testing.T) { signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) + propBlockHash := propBlock.Hash() + propBlockParts := propBlock.MakePartSet() incrementRound(cs2, cs3, cs4) - log.Info("### ONTO ROUND 2") + //XXX: this isnt gauranteed to get there before the timeoutPropose ... + cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer") + + <-newRoundCh + log.Notice("### ONTO ROUND 1") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! */ // now we're on a new round and not the proposer, - // so set proposal - cs1.mtx.Lock() - propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet() - cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts - cs1.mtx.Unlock() - <-cs1.NewStepCh() - - if cs1.LockedBlock != nil { + // but we should receive the proposal + select { + case re = <-proposalCh: + case <-timeoutProposeCh: + re = <-proposalCh + } + + rs = re.(*types.EventDataRoundState) + + if rs.LockedBlock != nil { t.Fatal("we should not be locked!") } log.Warn("new prop", "hash", fmt.Sprintf("%X", propBlockHash)) // go to prevote, prevote for proposal block - _, _ = <-voteChan, <-cs1.NewStepCh() + <-voteCh validatePrevote(t, cs1, 1, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it - donePrecommit := make(chan struct{}) - go func() { - select { - case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit - <-voteChan - case <-voteChan: // we went straight to Precommit - } - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() - // now lets add prevotes from everyone else for nil signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4) - <-donePrecommit + + <-voteCh // precommit // we should have precommitted validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) - // now we see precommits for nil - donePrecommitWait := make(chan struct{}) - go func() { - // the votes will bring us to new round - // we should timeut of it and go to prevote - <-cs1.NewStepCh() - <-timeoutChan - donePrecommitWait <- struct{}{} - }() signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) - <-donePrecommitWait + + <-timeoutWaitCh incrementRound(cs2, cs3, cs4) - log.Info("### ONTO ROUND 3") + <-newRoundCh + + log.Notice("### ONTO ROUND 2") /*Round3 we see the polka from round 1 but we shouldn't unlock! */ // timeout of propose - _, _ = <-cs1.NewStepCh(), <-timeoutChan + <-timeoutProposeCh // finish prevote - _, _ = <-voteChan, <-cs1.NewStepCh() + <-voteCh // we should prevote what we're locked on validatePrevote(t, cs1, 2, vss[0], propBlockHash) @@ -875,190 +788,138 @@ func TestLockPOLSafety1(t *testing.T) { log.Warn("Done adding prevotes!") - ensureNoNewStep(t, cs1) + // ensureNoNewStep(t, cs1) + // TODO: subscribe to NewStep ... + } // 4 vals. -// polka P1 at R1, P2 at R2, and P3 at R3, -// we lock on P1 at R1, don't see P2, and unlock using P3 at R3 -// then we should make sure we don't lock using P2 +// polka P0 at R0, P1 at R1, and P2 at R2, +// we lock on P0 at R0, don't see P1, and unlock using P2 at R2 +// then we should make sure we don't lock using P1 + +// What we want: +// dont see P0, lock on P1 at R1, dont unlock using P0 at R2 func TestLockPOLSafety2(t *testing.T) { cs1, vss := simpleConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] - cs1.newStepCh = make(chan *RoundState) // so it blocks - - timeoutChan := make(chan *types.EventDataRoundState) - voteChan := make(chan *types.EventDataVote) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- data.(*types.EventDataRoundState) - }) - evsw.AddListenerForEvent("tester", types.EventStringVote(), func(data types.EventData) { - vote := data.(*types.EventDataVote) - // we only fire for our own votes - if bytes.Equal(cs1.privValidator.Address, vote.Address) { - voteChan <- vote - } - }) - cs1.SetFireable(evsw) - // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh() + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutProposeCh := subscribeToEvent(cs1, types.EventStringTimeoutPropose()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + unlockCh := subscribeToEvent(cs1, types.EventStringUnlock()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) - theBlockHash := cs1.ProposalBlock.Hash() + // the block for R0: gets polkad but we miss it + // (even though we signed it, shhh) + _, propBlock0 := decideProposal(cs1, vss[0], cs1.Height, cs1.Round) + propBlockHash0 := propBlock0.Hash() + propBlockParts0 := propBlock0.MakePartSet() - donePrecommit := make(chan struct{}) - go func() { - <-voteChan - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() - signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4) - <-donePrecommit + // the others sign a polka but we don't see it + prevotes := signVoteMany(types.VoteTypePrevote, propBlockHash0, propBlockParts0.Header(), cs2, cs3, cs4) - // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) + // the block for round 1 + prop1, propBlock1 := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) + propBlockHash1 := propBlock1.Hash() + propBlockParts1 := propBlock1.MakePartSet() - donePrecommitWait := make(chan struct{}) - go func() { - <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() - // add precommits from the rest - signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) - <-donePrecommitWait + incrementRound(cs2, cs3, cs4) - // before we time out into new round, set next proposer - // and next proposal block - _, v1 := cs1.Validators.GetByAddress(vss[0].Address) - v1.VotingPower = 1 - if updated := cs1.Validators.Update(v1); !updated { - t.Fatal("failed to update validator") - } + cs1.updateRoundStep(0, RoundStepPrecommitWait) - prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1) - - incrementRound(cs2, cs3, cs4) + log.Notice("### ONTO Round 1") + // jump in at round 1 + height := cs1.Height + startTestRound(cs1, height, 1) + <-newRoundCh - // timeout to new round - <-timeoutChan + cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer") + <-proposalCh - log.Info("### ONTO Round 2") - /*Round2 - // we timeout and prevote our lock - // a polka happened but we didn't see it! - */ + <-voteCh // prevote - // now we're on a new round and not the proposer, so wait for timeout - _, _ = <-cs1.NewStepCh(), <-timeoutChan - // go to prevote, prevote for locked block - _, _ = <-voteChan, <-cs1.NewStepCh() - validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) + signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash1, propBlockParts1.Header(), cs2, cs3, cs4) - // the others sign a polka but we don't see it - prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4) + <-voteCh // precommit + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash1, propBlockHash1) - // once we see prevotes for the next round we'll skip ahead + // add precommits from the rest + signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs4) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, propBlockHash1, propBlockParts1.Header()) incrementRound(cs2, cs3, cs4) - log.Info("### ONTO Round 3") - /*Round3 - a polka for nil causes us to unlock - */ + // timeout of precommit wait to new round + <-timeoutWaitCh - // these prevotes will send us straight to precommit at the higher round - donePrecommit = make(chan struct{}) - go func() { - select { - case <-cs1.NewStepCh(): // we're in PrevoteWait, go to Precommit - <-voteChan - case <-voteChan: // we went straight to Precommit - } - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() - // now lets add prevotes from everyone else for nil - signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4) - <-donePrecommit - - // we should have unlocked - // NOTE: we don't lock on nil, so LockedRound is still 0 - validatePrecommit(t, cs1, 2, 0, vss[0], nil, nil) - - donePrecommitWait = make(chan struct{}) - go func() { - // the votes will bring us to new round right away - // we should timeut of it and go to prevote - <-cs1.NewStepCh() - // set the proposal block to be that which got a polka in R2 - cs1.mtx.Lock() - cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet() - cs1.mtx.Unlock() - // timeout into prevote, finish prevote - _, _, _ = <-timeoutChan, <-voteChan, <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() - signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3) - <-donePrecommitWait + // in round 2 we see the polkad block from round 0 + newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0) + if err := cs3.SignProposal(chainID, newProp); err != nil { + t.Fatal(err) + } + cs1.SetProposalAndBlock(newProp, propBlock0, propBlockParts0, "some peer") + addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) // add the pol votes - log.Info("### ONTO ROUND 4") - /*Round4 - we see the polka from R2 - make sure we don't lock because of it! + <-newRoundCh + log.Notice("### ONTO Round 2") + /*Round2 + // now we see the polka from round 1, but we shouldnt unlock */ - // new round and not proposer - // (we already timed out and stepped into prevote) - - log.Warn("adding prevotes from round 2") - - addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4) - log.Warn("Done adding prevotes!") - - // we should prevote it now - validatePrevote(t, cs1, 3, vss[0], cs1.ProposalBlock.Hash()) + select { + case <-timeoutProposeCh: + <-proposalCh + case <-proposalCh: + } - // but we shouldn't precommit it - precommits := cs1.Votes.Precommits(3) - vote := precommits.GetByIndex(0) - if vote != nil { - t.Fatal("validator precommitted at round 4 based on an old polka") + select { + case <-unlockCh: + t.Fatal("validator unlocked using an old polka") + case <-voteCh: + // prevote our locked block } + validatePrevote(t, cs1, 2, vss[0], propBlockHash1) + } //------------------------------------------------------------------------------------------ // SlashingSuite +// TODO: Slashing +/* func TestSlashingPrevotes(t *testing.T) { cs1, vss := simpleConsensusState(2) cs2 := vss[1] - cs1.newStepCh = make(chan *RoundState) // so it blocks + + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + startTestRound(cs1, cs1.Height, 0) + <-newRoundCh + re := <-proposalCh + <-voteCh // prevote + + rs := re.(*types.EventDataRoundState) // we should now be stuck in limbo forever, waiting for more prevotes // add one for a different block should cause us to go into prevote wait hash := cs1.ProposalBlock.Hash() hash[0] = byte(hash[0]+1) % 255 - signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, rs.ProposalBlockParts.Header()) - // pass prevote wait - <-cs1.NewStepCh() + <-timeoutWaitCh // NOTE: we have to send the vote for different block first so we don't just go into precommit round right // away and ignore more prevotes (and thus fail to slash!) // add the conflicting vote - signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header()) // XXX: Check for existence of Dupeout info } @@ -1066,35 +927,38 @@ func TestSlashingPrevotes(t *testing.T) { func TestSlashingPrecommits(t *testing.T) { cs1, vss := simpleConsensusState(2) cs2 := vss[1] - cs1.newStepCh = make(chan *RoundState) // so it blocks + + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + startTestRound(cs1, cs1.Height, 0) + <-newRoundCh + re := <-proposalCh + <-voteCh // prevote // add prevote from cs2 - signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header()) - // wait to finish precommit - <-cs1.NewStepCh() + <-voteCh // precommit // we should now be stuck in limbo forever, waiting for more prevotes // add one for a different block should cause us to go into prevote wait - hash := cs1.ProposalBlock.Hash() + hash := rs.ProposalBlock.Hash() hash[0] = byte(hash[0]+1) % 255 - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header()) - - // pass prevote wait - <-cs1.NewStepCh() + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, rs.ProposalBlockParts.Header()) // NOTE: we have to send the vote for different block first so we don't just go into precommit round right // away and ignore more prevotes (and thus fail to slash!) // add precommit from cs2 - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header()) // XXX: Check for existence of Dupeout info } +*/ //------------------------------------------------------------------------------------------ // CatchupSuite @@ -1107,71 +971,61 @@ func TestSlashingPrecommits(t *testing.T) { func TestHalt1(t *testing.T) { cs1, vss := simpleConsensusState(4) cs2, cs3, cs4 := vss[1], vss[2], vss[3] - cs1.newStepCh = make(chan *RoundState) // so it blocks - timeoutChan := make(chan struct{}) - evsw := events.NewEventSwitch() - evsw.OnStart() - evsw.AddListenerForEvent("tester", types.EventStringTimeoutWait(), func(data types.EventData) { - timeoutChan <- struct{}{} - }) - cs1.SetFireable(evsw) + proposalCh := subscribeToEvent(cs1, types.EventStringCompleteProposal()) + timeoutWaitCh := subscribeToEvent(cs1, types.EventStringTimeoutWait()) + newRoundCh := subscribeToEvent(cs1, types.EventStringNewRound()) + newBlockCh := subscribeToEvent(cs1, types.EventStringNewBlock()) + voteCh := subscribeToVoter(cs1, cs1.privValidator.Address) // start round and wait for propose and prevote - go cs1.EnterNewRound(cs1.Height, 0) - _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() + startTestRound(cs1, cs1.Height, 0) + <-newRoundCh + re := <-proposalCh + rs := re.(*types.EventDataRoundState) + propBlock := rs.ProposalBlock + propBlockParts := propBlock.MakePartSet() - theBlockHash := cs1.ProposalBlock.Hash() + <-voteCh // prevote - donePrecommit := make(chan struct{}) - go func() { - <-cs1.NewStepCh() - donePrecommit <- struct{}{} - }() - signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs3, cs4) - <-donePrecommit + signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlock.Hash(), propBlockParts.Header(), cs3, cs4) + <-voteCh // precommit // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) + validatePrecommit(t, cs1, 0, 0, vss[0], propBlock.Hash(), propBlock.Hash()) - donePrecommitWait := make(chan struct{}) - go func() { - <-cs1.NewStepCh() - donePrecommitWait <- struct{}{} - }() // add precommits from the rest signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, nil, types.PartSetHeader{}) // didnt receive proposal - signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) + signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs3, propBlock.Hash(), propBlockParts.Header()) // we receive this later, but cs3 might receive it earlier and with ours will go to commit! - precommit4 := signVote(cs4, types.VoteTypePrecommit, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header()) - <-donePrecommitWait + precommit4 := signVote(cs4, types.VoteTypePrecommit, propBlock.Hash(), propBlockParts.Header()) incrementRound(cs2, cs3, cs4) // timeout to new round - <-timeoutChan + <-timeoutWaitCh + re = <-newRoundCh + rs = re.(*types.EventDataRoundState) - log.Info("### ONTO ROUND 2") + log.Notice("### ONTO ROUND 1") /*Round2 // we timeout and prevote our lock // a polka happened but we didn't see it! */ // go to prevote, prevote for locked block - _, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh() - validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) + <-voteCh // prevote + validatePrevote(t, cs1, 0, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round addVoteToFrom(cs1, cs4, precommit4) // receiving that precommit should take us straight to commit - ensureNewStep(t, cs1) - log.Warn("done enter commit!") - - // update to state - ensureNewStep(t, cs1) + <-newBlockCh + re = <-newRoundCh + rs = re.(*types.EventDataRoundState) - if cs1.Height != 2 { + if rs.Height != 2 { t.Fatal("expected height to increment") } } diff --git a/types/block.go b/types/block.go index 752d6a562..de0eb2caf 100644 --- a/types/block.go +++ b/types/block.go @@ -62,8 +62,12 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash } func (b *Block) FillHeader() { - b.LastValidationHash = b.LastValidation.Hash() - b.DataHash = b.Data.Hash() + if b.LastValidationHash == nil { + b.LastValidationHash = b.LastValidation.Hash() + } + if b.DataHash == nil { + b.DataHash = b.Data.Hash() + } } // Computes and returns the block hash.