Esempio n. 1
0
func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs RoundState) {
	log.Debug("Received tock", "timeout", ti.Duration, "height", ti.Height, "round", ti.Round, "step", ti.Step)

	// timeouts must be for current height, round, step
	if ti.Height != rs.Height || ti.Round < rs.Round || (ti.Round == rs.Round && ti.Step < rs.Step) {
		log.Debug("Ignoring tock because we're ahead", "height", rs.Height, "round", rs.Round, "step", rs.Step)
		return
	}

	// the timeout will now cause a state transition
	cs.mtx.Lock()
	defer cs.mtx.Unlock()

	switch ti.Step {
	case RoundStepNewHeight:
		// NewRound event fired from enterNewRound.
		// XXX: should we fire timeout here?
		cs.enterNewRound(ti.Height, 0)
	case RoundStepPropose:
		cs.evsw.FireEvent(types.EventStringTimeoutPropose(), cs.RoundStateEvent())
		cs.enterPrevote(ti.Height, ti.Round)
	case RoundStepPrevoteWait:
		cs.evsw.FireEvent(types.EventStringTimeoutWait(), cs.RoundStateEvent())
		cs.enterPrecommit(ti.Height, ti.Round)
	case RoundStepPrecommitWait:
		cs.evsw.FireEvent(types.EventStringTimeoutWait(), cs.RoundStateEvent())
		cs.enterNewRound(ti.Height, ti.Round+1)
	default:
		panic(Fmt("Invalid timeout step: %v", ti.Step))
	}

}
Esempio n. 2
0
func TestBadProposal(t *testing.T) {
	css, privVals := simpleConsensusState(2)
	cs1, cs2 := css[0], css[1]
	cs1.newStepCh = make(chan *RoundState) // so it blocks

	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)

	// make the second validator the proposer
	propBlock := changeProposer(t, cs1, cs2)

	// make the block bad by tampering with statehash
	stateHash := propBlock.StateHash
	stateHash[0] = byte((stateHash[0] + 1) % 255)
	propBlock.StateHash = stateHash
	propBlockParts := propBlock.MakePartSet()
	proposal := types.NewProposal(cs2.Height, cs2.Round, propBlockParts.Header(), cs2.Votes.POLRound())
	if err := cs2.privValidator.SignProposal(cs2.state.ChainID, proposal); err != nil {
		t.Fatal("failed to sign bad proposal", err)
	}

	// start round
	cs1.EnterNewRound(cs1.Height, 0, false)

	// now we're on a new round and not the proposer
	<-cs1.NewStepCh()
	// so set the proposal block (and fix voting power)
	cs1.mtx.Lock()
	cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = proposal, propBlock, propBlockParts
	fixVotingPower(t, cs1, privVals[1].Address)
	cs1.mtx.Unlock()
	// and wait for timeout
	<-timeoutChan

	// go to prevote, prevote for nil (proposal is bad)
	<-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[0], nil)

	// add bad prevote from cs2. we should precommit nil
	signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header())
	_, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh()
	validatePrecommit(t, cs1, 0, 0, privVals[0], nil, nil)
	signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header())
}
Esempio n. 3
0
// Enter: +2/3 precommits for nil at (height,round-1)
// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1)
// Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height)
// NOTE: cs.StartTime was already set for height.
func (cs *ConsensusState) EnterNewRound(height int, round int, timedOut bool) {
	cs.mtx.Lock()
	defer cs.mtx.Unlock()
	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
	}

	if timedOut {
		cs.evsw.FireEvent(types.EventStringTimeoutWait(), cs.RoundStateEvent())
	}

	if now := time.Now(); cs.StartTime.After(now) {
		log.Warn("Need to set a buffer and log.Warn() here for sanity.", "startTime", cs.StartTime, "now", now)
	}
	log.Notice(Fmt("EnterNewRound(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))

	// Increment validators if necessary
	validators := cs.Validators
	if cs.Round < round {
		validators = validators.Copy()
		validators.IncrementAccum(round - cs.Round)
	}

	// Setup new round
	cs.Round = round
	cs.Step = RoundStepNewRound
	cs.Validators = validators
	if round == 0 {
		// We've already reset these upon new height,
		// and meanwhile we might have received a proposal
		// for round 0.
	} else {
		cs.Proposal = nil
		cs.ProposalBlock = nil
		cs.ProposalBlockParts = nil
	}
	cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping

	cs.evsw.FireEvent(types.EventStringNewRound(), cs.RoundStateEvent())

	// Immediately go to EnterPropose.
	go cs.EnterPropose(height, round)
}
Esempio n. 4
0
// 4 vals.
// we receive a final precommit after going into next round, but others might have gone to commit already!
func TestHalt1(t *testing.T) {
	cs1, vss := randConsensusState(4)
	cs2, cs3, cs4 := vss[1], vss[2], vss[3]

	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)
	newBlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewBlock(), 1)
	voteCh := subscribeToVoter(cs1, cs1.privValidator.Address)

	// start round and wait for propose and prevote
	startTestRound(cs1, cs1.Height, 0)
	<-newRoundCh
	re := <-proposalCh
	rs := re.(types.EventDataRoundState).RoundState.(*RoundState)
	propBlock := rs.ProposalBlock
	propBlockParts := propBlock.MakePartSet()

	<-voteCh // prevote

	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], propBlock.Hash(), propBlock.Hash())

	// add precommits from the rest
	signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, nil, types.PartSetHeader{}) // didnt receive proposal
	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, propBlock.Hash(), propBlockParts.Header())

	incrementRound(cs2, cs3, cs4)

	// timeout to new round
	<-timeoutWaitCh
	re = <-newRoundCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)

	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
	<-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
	<-newBlockCh
	re = <-newRoundCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)

	if rs.Height != 2 {
		t.Fatal("expected height to increment")
	}
}
Esempio n. 5
0
// What we want:
// dont see P0, lock on P1 at R1, dont unlock using P0 at R2
func TestLockPOLSafety2(t *testing.T) {
	cs1, vss := randConsensusState(4)
	cs2, cs3, cs4 := vss[1], vss[2], vss[3]

	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)
	unlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringUnlock(), 1)
	voteCh := subscribeToVoter(cs1, cs1.privValidator.Address)

	// 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()

	// the others sign a polka but we don't see it
	prevotes := signVoteMany(types.VoteTypePrevote, propBlockHash0, propBlockParts0.Header(), cs2, cs3, cs4)

	// the block for round 1
	prop1, propBlock1 := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1)
	propBlockHash1 := propBlock1.Hash()
	propBlockParts1 := propBlock1.MakePartSet()

	incrementRound(cs2, cs3, cs4)

	cs1.updateRoundStep(0, RoundStepPrecommitWait)

	log.Notice("### ONTO Round 1")
	// jump in at round 1
	height := cs1.Height
	startTestRound(cs1, height, 1)
	<-newRoundCh

	cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer")
	<-proposalCh

	<-voteCh // prevote

	signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash1, propBlockParts1.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)

	// 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)

	// timeout of precommit wait to new round
	<-timeoutWaitCh

	// 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

	<-newRoundCh
	log.Notice("### ONTO Round 2")
	/*Round2
	// now we see the polka from round 1, but we shouldnt unlock
	*/

	select {
	case <-timeoutProposeCh:
		<-proposalCh
	case <-proposalCh:
	}

	select {
	case <-unlockCh:
		t.Fatal("validator unlocked using an old polka")
	case <-voteCh:
		// prevote our locked block
	}
	validatePrevote(t, cs1, 2, vss[0], propBlockHash1)

}
Esempio n. 6
0
// 4 vals
// a polka at round 1 but we miss it
// then a polka at round 2 that we lock on
// then we see the polka from round 1 but shouldn't unlock
func TestLockPOLSafety1(t *testing.T) {
	cs1, vss := randConsensusState(4)
	cs2, cs3, cs4 := vss[1], vss[2], vss[3]

	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)
	voteCh := subscribeToVoter(cs1, cs1.privValidator.Address)

	// start round and wait for propose and prevote
	startTestRound(cs1, cs1.Height, 0)
	<-newRoundCh
	re := <-proposalCh
	rs := re.(types.EventDataRoundState).RoundState.(*RoundState)
	propBlock := rs.ProposalBlock

	<-voteCh // prevote

	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")
		}*/

	log.Warn("old prop", "hash", fmt.Sprintf("%X", propBlock.Hash()))

	// we do see them precommit nil
	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)

	//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,
	// but we should receive the proposal
	select {
	case re = <-proposalCh:
	case <-timeoutProposeCh:
		re = <-proposalCh
	}

	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)

	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
	<-voteCh
	validatePrevote(t, cs1, 1, vss[0], propBlockHash)

	// now we see the others prevote for it, so we should lock on it
	signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4)

	<-voteCh // precommit

	// we should have precommitted
	validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash)

	signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3)

	<-timeoutWaitCh

	incrementRound(cs2, cs3, cs4)

	<-newRoundCh

	log.Notice("### ONTO ROUND 2")
	/*Round3
	we see the polka from round 1 but we shouldn't unlock!
	*/

	// timeout of propose
	<-timeoutProposeCh

	// finish prevote
	<-voteCh

	// we should prevote what we're locked on
	validatePrevote(t, cs1, 2, vss[0], propBlockHash)

	newStepCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRoundStep(), 1)

	// add prevotes from the earlier round
	addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4)

	log.Warn("Done adding prevotes!")

	ensureNoNewStep(newStepCh)
}
Esempio n. 7
0
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLUnlock(t *testing.T) {
	cs1, vss := randConsensusState(4)
	cs2, cs3, cs4 := vss[1], vss[2], vss[3]

	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)
	unlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringUnlock(), 1)
	voteCh := subscribeToVoter(cs1, cs1.privValidator.Address)

	// everything done from perspective of cs1

	/*
		Round1 (cs1, B) // B B B B // B nil B nil

		eg. didn't see the 2/3 prevotes
	*/

	// start round and wait for propose and prevote
	startTestRound(cs1, cs1.Height, 0)
	<-newRoundCh
	re := <-proposalCh
	rs := re.(types.EventDataRoundState).RoundState.(*RoundState)
	theBlockHash := rs.ProposalBlock.Hash()

	<-voteCh // prevote

	signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4)

	<-voteCh //precommit

	// the proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash)

	// 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())

	// 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
	re = <-timeoutWaitCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)
	lockedBlockHash := rs.LockedBlock.Hash()

	//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 nil nil nil // nil nil nil _

		cs1 unlocks!
	*/

	// now we're on a new round and not the proposer,
	// but we should receive the proposal
	select {
	case <-proposalCh:
	case <-timeoutProposeCh:
		<-proposalCh
	}

	// go to prevote, prevote for locked block (not proposal)
	<-voteCh
	validatePrevote(t, cs1, 0, vss[0], lockedBlockHash)
	// now lets add prevotes from everyone else for nil (a polka!)
	signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4)

	// 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)

	signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3)
	<-newRoundCh
}
Esempio n. 8
0
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLRelock(t *testing.T) {
	cs1, vss := randConsensusState(4)
	cs2, cs3, cs4 := vss[1], vss[2], vss[3]

	timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)
	newBlockCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewBlock(), 1)

	log.Debug("cs2 last round", "lr", cs2.PrivValidator.LastRound)

	// everything done from perspective of cs1

	/*
		Round1 (cs1, B) // B B B B// B nil B nil

		eg. cs2 and cs4 didn't see the 2/3 prevotes
	*/

	// start round and wait for propose and prevote
	startTestRound(cs1, cs1.Height, 0)

	<-newRoundCh
	re := <-proposalCh
	rs := re.(types.EventDataRoundState).RoundState.(*RoundState)
	theBlockHash := rs.ProposalBlock.Hash()

	<-voteCh // prevote

	signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4)
	_, _, _ = <-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)

	// 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())
	_, _, _ = <-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
	<-timeoutWaitCh

	//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 _)

		cs1 changes lock!
	*/

	// now we're on a new round and not the proposer
	// but we should receive the proposal
	select {
	case <-proposalCh:
	case <-timeoutProposeCh:
		<-proposalCh
	}

	// go to prevote, prevote for locked block (not proposal), move on
	<-voteCh
	validatePrevote(t, cs1, 0, vss[0], theBlockHash)

	// now lets add prevotes from everyone else for the new block
	signAddVoteToFromMany(types.VoteTypePrevote, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3, cs4)
	_, _, _ = <-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)

	signAddVoteToFromMany(types.VoteTypePrecommit, cs1, propBlockHash, propBlockParts.Header(), cs2, cs3)
	_, _ = <-voteCh, <-voteCh

	be := <-newBlockCh
	b := be.(types.EventDataNewBlock)
	re = <-newRoundCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)
	if rs.Height != 2 {
		t.Fatal("Expected height to increment")
	}

	if !bytes.Equal(b.Block.Hash(), propBlockHash) {
		t.Fatal("Expected new block to be proposal block")
	}
}
Esempio n. 9
0
// two validators, 4 rounds.
// two vals take turns proposing. val1 locks on first one, precommits nil on everything else
func TestLockNoPOL(t *testing.T) {
	cs1, vss := randConsensusState(2)
	cs2 := vss[1]
	height := cs1.Height

	timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1)
	timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1)
	voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1)
	proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1)
	newRoundCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringNewRound(), 1)

	/*
		Round1 (cs1, B) // B B // B B2
	*/

	// start round and wait for prevote
	cs1.enterNewRound(height, 0)
	cs1.startRoutines(0)

	re := <-proposalCh
	rs := re.(types.EventDataRoundState).RoundState.(*RoundState)
	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

	<-voteCh // precommit

	// the proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash)

	// 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 := make([]byte, len(theBlockHash))
	copy(hash, theBlockHash)
	hash[0] = byte((hash[0] + 1) % 255)
	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
	<-timeoutWaitCh

	///

	<-newRoundCh
	log.Notice("#### ONTO ROUND 1")
	/*
		Round2 (cs1, B) // B B2
	*/

	incrementRound(cs2)

	// now we're on a new round and not the proposer, so wait for timeout
	re = <-timeoutProposeCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)

	if rs.ProposalBlock != nil {
		t.Fatal("Expected proposal block to be nil")
	}

	// wait to finish prevote
	<-voteCh

	// we should have prevoted our locked block
	validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash())

	// add a conflicting prevote from the other validator
	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
	<-timeoutWaitCh

	<-voteCh // precommit

	// the proposed block should still be locked and our precommit added
	// we should precommit nil and be locked on the proposal
	validatePrecommit(t, cs1, 1, 0, vss[0], nil, theBlockHash)

	// 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, 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
	<-timeoutWaitCh

	<-newRoundCh
	log.Notice("#### ONTO ROUND 2")
	/*
		Round3 (cs2, _) // B, B2
	*/

	incrementRound(cs2)

	re = <-proposalCh
	rs = re.(types.EventDataRoundState).RoundState.(*RoundState)

	// now we're on a new round and are the proposer
	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)
	}

	<-voteCh // prevote

	validatePrevote(t, cs1, 2, vss[0], rs.LockedBlock.Hash())

	signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, rs.ProposalBlock.MakePartSet().Header())
	<-voteCh

	<-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, rs.ProposalBlock.MakePartSet().Header()) // NOTE: conflicting precommits at same height
	<-voteCh

	<-timeoutWaitCh

	// before we time out into new round, set next proposal block
	prop, propBlock := decideProposal(cs1, cs2, cs2.Height, cs2.Round+1)
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	incrementRound(cs2)

	<-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.SetProposalAndBlock(prop, propBlock, propBlock.MakePartSet(), "")

	<-proposalCh
	<-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

	<-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
}
Esempio n. 10
0
// Enter: +2/3 precomits for block or nil.
// Enter: `timeoutPrevote` after any +2/3 prevotes.
// Enter: any +2/3 precommits for next round.
// Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round)
// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil,
// else, precommit nil otherwise.
func (cs *ConsensusState) EnterPrecommit(height int, round int, timedOut bool) {
	cs.mtx.Lock()
	defer cs.mtx.Unlock()
	if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) {
		log.Debug(Fmt("EnterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))
		return
	}

	if timedOut {
		cs.evsw.FireEvent(types.EventStringTimeoutWait(), cs.RoundStateEvent())
	}

	log.Info(Fmt("EnterPrecommit(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))

	defer func() {
		// Done EnterPrecommit:
		cs.Round = round
		cs.Step = RoundStepPrecommit
		cs.newStepCh <- cs.getRoundState()
	}()

	hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority()

	// If we don't have a polka, we must precommit nil
	if !ok {
		if cs.LockedBlock != nil {
			log.Info("EnterPrecommit: No +2/3 prevotes during EnterPrecommit while we're locked. Precommitting nil")
		} else {
			log.Info("EnterPrecommit: No +2/3 prevotes during EnterPrecommit. Precommitting nil.")
		}
		cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
		return
	}

	// At this point +2/3 prevoted for a particular block or nil
	cs.evsw.FireEvent(types.EventStringPolka(), cs.RoundStateEvent())

	// the latest POLRound should be this round
	if cs.Votes.POLRound() < round {
		PanicSanity(Fmt("This POLRound should be %v but got %", round, cs.Votes.POLRound()))
	}

	// +2/3 prevoted nil. Unlock and precommit nil.
	if len(hash) == 0 {
		if cs.LockedBlock == nil {
			log.Info("EnterPrecommit: +2/3 prevoted for nil.")
		} else {
			log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking")
			cs.LockedRound = 0
			cs.LockedBlock = nil
			cs.LockedBlockParts = nil
			cs.evsw.FireEvent(types.EventStringUnlock(), cs.RoundStateEvent())
		}
		cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
		return
	}

	// 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) {
		log.Info("EnterPrecommit: +2/3 prevoted locked block. Relocking")
		cs.LockedRound = round
		cs.evsw.FireEvent(types.EventStringRelock(), cs.RoundStateEvent())
		cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader)
		return
	}

	// If +2/3 prevoted for proposal block, stage and precommit it
	if cs.ProposalBlock.HashesTo(hash) {
		log.Info("EnterPrecommit: +2/3 prevoted proposal block. Locking", "hash", hash)
		// Validate the block.
		if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil {
			PanicConsensus(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err))
		}
		cs.LockedRound = round
		cs.LockedBlock = cs.ProposalBlock
		cs.LockedBlockParts = cs.ProposalBlockParts
		cs.evsw.FireEvent(types.EventStringLock(), cs.RoundStateEvent())
		cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader)
		return
	}

	// There was a polka in this round for a block we don't have.
	// Fetch that block, unlock, and precommit nil.
	// The +2/3 prevotes for this round is the POL for our unlock.
	// TODO: In the future save the POL prevotes for justification.
	cs.LockedRound = 0
	cs.LockedBlock = nil
	cs.LockedBlockParts = nil
	if !cs.ProposalBlockParts.HasHeader(partsHeader) {
		cs.ProposalBlock = nil
		cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader)
	}
	cs.evsw.FireEvent(types.EventStringUnlock(), cs.RoundStateEvent())
	cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{})
	return
}
Esempio n. 11
0
// 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
func TestLockPOLSafety2(t *testing.T) {
	css, privVals := simpleConsensusState(4)
	cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[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
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh()

	theBlockHash := cs1.ProposalBlock.Hash()

	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 proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, privVals[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(privVals[0].Address)
	v1.VotingPower = 1
	if updated := cs1.Validators.Update(v1); !updated {
		t.Fatal("failed to update validator")
	}

	cs2.decideProposal(cs2.Height, cs2.Round+1)
	prop, propBlock := cs2.Proposal, cs2.ProposalBlock
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	incrementRound(cs2, cs3, cs4)

	// timeout to new round
	<-timeoutChan

	log.Info("### ONTO Round 2")
	/*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 wait for timeout
	_, _ = <-cs1.NewStepCh(), <-timeoutChan
	// go to prevote, prevote for locked block
	_, _ = <-voteChan, <-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash())

	// the others sign a polka but we don't see it
	prevotes := signVoteMany(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet().Header(), cs2, cs3, cs4)

	// once we see prevotes for the next round we'll skip ahead

	incrementRound(cs2, cs3, cs4)

	log.Info("### ONTO Round 3")
	/*Round3
	a polka for nil causes us to unlock
	*/

	// 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, privVals[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

	log.Info("### ONTO ROUND 4")
	/*Round4
	we see the polka from R2
	make sure we don't lock because of it!
	*/
	// 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, privVals[0], cs1.ProposalBlock.Hash())

	// 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")
	}
}
Esempio n. 12
0
// 4 vals
// a polka at round 1 but we miss it
// then a polka at round 2 that we lock on
// then we see the polka from round 1 but shouldn't unlock
func TestLockPOLSafety1(t *testing.T) {
	css, privVals := simpleConsensusState(4)
	cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[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
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh()

	propBlock := cs1.ProposalBlock

	validatePrevote(t, cs1, 0, privVals[0], cs1.ProposalBlock.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(privVals[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()))

	// we do see them precommit nil
	signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, types.PartSetHeader{}, cs2, cs3, cs4)

	cs2.decideProposal(cs2.Height, cs2.Round+1)
	prop, propBlock := cs2.Proposal, cs2.ProposalBlock
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	incrementRound(cs2, cs3, cs4)

	log.Info("### ONTO ROUND 2")
	/*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,
	<-cs1.NewStepCh()
	// so set proposal
	cs1.mtx.Lock()
	propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet()
	cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts
	cs1.mtx.Unlock()
	// and wait for timeout
	<-timeoutChan
	if cs1.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()
	validatePrevote(t, cs1, 1, privVals[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

	// we should have precommitted
	validatePrecommit(t, cs1, 1, 1, privVals[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

	incrementRound(cs2, cs3, cs4)

	log.Info("### ONTO ROUND 3")
	/*Round3
	we see the polka from round 1 but we shouldn't unlock!
	*/

	// timeout of propose
	_, _ = <-cs1.NewStepCh(), <-timeoutChan

	// finish prevote
	_, _ = <-voteChan, <-cs1.NewStepCh()

	// we should prevote what we're locked on
	validatePrevote(t, cs1, 2, privVals[0], propBlockHash)

	// add prevotes from the earlier round
	addVoteToFromMany(cs1, prevotes, cs2, cs3, cs4)

	log.Warn("Done adding prevotes!")

	ensureNoNewStep(t, cs1)
}
Esempio n. 13
0
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLUnlock(t *testing.T) {
	css, privVals := simpleConsensusState(4)
	cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[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)

	// everything done from perspective of cs1

	/*
		Round1 (cs1, B) // B B B B // B nil B nil

		eg. didn't see the 2/3 prevotes
	*/

	// start round and wait for propose and prevote
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _, _ = <-cs1.NewStepCh(), <-voteChan, <-cs1.NewStepCh()

	theBlockHash := cs1.ProposalBlock.Hash()

	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 proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, privVals[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(privVals[0].Address)
	v1.VotingPower = 1
	if updated := cs1.Validators.Update(v1); !updated {
		t.Fatal("failed to update validator")
	}

	cs2.decideProposal(cs2.Height, cs2.Round+1)
	prop, propBlock := cs2.Proposal, cs2.ProposalBlock
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	incrementRound(cs2, cs3, cs4)

	// timeout to new round
	<-timeoutChan

	log.Info("#### ONTO ROUND 2")
	/*
		Round2 (cs2, C) // B nil nil nil // nil nil nil _

		cs1 unlocks!
	*/

	// now we're on a new round and not the proposer,
	<-cs1.NewStepCh()
	cs1.mtx.Lock()
	// so set the proposal block
	cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlock.MakePartSet()
	lockedBlockHash := cs1.LockedBlock.Hash()
	cs1.mtx.Unlock()
	// and wait for timeout
	<-timeoutChan

	// go to prevote, prevote for locked block (not proposal)
	_, _ = <-voteChan, <-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[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
	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, 1, 0, privVals[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
}
Esempio n. 14
0
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLRelock(t *testing.T) {
	css, privVals := simpleConsensusState(4)
	cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[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)

	// everything done from perspective of cs1

	/*
		Round1 (cs1, B) // B B B B// B nil B nil

		eg. cs2 and cs4 didn't see the 2/3 prevotes
	*/

	// start round and wait for propose and prevote
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _, _ = <-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{}{}
	}()
	signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs2, cs3, cs4)
	<-donePrecommit

	// the proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, privVals[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(privVals[0].Address)
	v1.VotingPower = 1
	if updated := cs1.Validators.Update(v1); !updated {
		t.Fatal("failed to update validator")
	}

	cs2.decideProposal(cs2.Height, cs2.Round+1)
	prop, propBlock := cs2.Proposal, cs2.ProposalBlock
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	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)
	}

	log.Info("### ONTO ROUND 2")

	/*
		Round2 (cs2, C) // B C C C // C C C _)

		cs1 changes lock!
	*/

	// now we're on a new round and not the proposer
	<-cs1.NewStepCh()
	cs1.mtx.Lock()
	// so set the proposal block
	propBlockHash, propBlockParts := propBlock.Hash(), propBlock.MakePartSet()
	cs1.Proposal, cs1.ProposalBlock, cs1.ProposalBlockParts = prop, propBlock, propBlockParts
	cs1.mtx.Unlock()
	// and wait for timeout
	te = <-timeoutChan
	if te.Step != RoundStepPropose.String() {
		t.Fatalf("expected to timeout of propose. got %v", te.Step)
	}
	// go to prevote, prevote for locked block (not proposal), move on
	_, _ = <-voteChan, <-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[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

	// we should have unlocked and locked on the new block
	validatePrecommit(t, cs1, 1, 1, privVals[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

	<-cs1.NewStepCh()
	rs := <-cs1.NewStepCh()
	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")
	}
}
Esempio n. 15
0
// two validators, 4 rounds.
// val1 proposes the first 2 rounds, and is locked in the first.
// val2 proposes the next two. val1 should precommit nil on all (except first where he locks)
func TestLockNoPOL(t *testing.T) {
	css, privVals := simpleConsensusState(2)
	cs1, cs2 := css[0], css[1]
	cs1.newStepCh = make(chan *RoundState) // so it blocks

	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)

	/*
		Round1 (cs1, B) // B B // B B2
	*/

	// start round and wait for propose and prevote
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh()

	// 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())

	cs1.mtx.Lock() // XXX: sigh
	theBlockHash := cs1.ProposalBlock.Hash()
	cs1.mtx.Unlock()

	// wait to finish precommit
	<-cs1.NewStepCh()

	// the proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash)

	// 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[0] = byte((hash[0] + 1) % 255)
	signAddVoteToFrom(types.VoteTypePrecommit, cs1, cs2, hash, cs1.ProposalBlockParts.Header())

	// (note we're entering precommit for a second time this round)
	// but with invalid args. then we EnterPrecommitWait, and the timeout to new round
	_, _ = <-cs1.NewStepCh(), <-timeoutChan

	log.Info("#### ONTO ROUND 2")
	/*
		Round2 (cs1, B) // B B2
	*/

	incrementRound(cs2)

	// go to prevote
	<-cs1.NewStepCh()

	// now we're on a new round and the proposer
	if cs1.ProposalBlock != cs1.LockedBlock {
		t.Fatalf("Expected proposal block to be locked block. Got %v, Expected %v", cs1.ProposalBlock, cs1.LockedBlock)
	}

	// wait to finish prevote
	<-cs1.NewStepCh()

	// we should have prevoted our locked block
	validatePrevote(t, cs1, 1, privVals[0], cs1.LockedBlock.Hash())

	// add a conflicting prevote from the other validator
	signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header())

	// now we're going to enter prevote again, but with invalid args
	// and then prevote wait, which should timeout. then wait for precommit
	_, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh()

	// the proposed block should still be locked and our precommit added
	// we should precommit nil and be locked on the proposal
	validatePrecommit(t, cs1, 1, 0, privVals[0], nil, theBlockHash)

	// 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())

	// (note we're entering precommit for a second time this round, but with invalid args
	// then we EnterPrecommitWait and timeout into NewRound
	_, _ = <-cs1.NewStepCh(), <-timeoutChan

	log.Info("#### ONTO ROUND 3")
	/*
		Round3 (cs2, _) // B, B2
	*/

	incrementRound(cs2)

	// now we're on a new round and not the proposer, so wait for timeout
	_, _ = <-cs1.NewStepCh(), <-timeoutChan
	if cs1.ProposalBlock != nil {
		t.Fatal("Expected proposal block to be nil")
	}

	// go to prevote, prevote for locked block
	<-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash())

	// TODO: quick fastforward to new round, set proposer
	signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, hash, cs1.ProposalBlockParts.Header())
	_, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh()
	validatePrecommit(t, cs1, 2, 0, privVals[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

	<-cs1.NewStepCh()

	// before we time out into new round, set next proposer
	// and next proposal block
	_, v1 := cs1.Validators.GetByAddress(privVals[0].Address)
	v1.VotingPower = 1
	if updated := cs1.Validators.Update(v1); !updated {
		t.Fatal("failed to update validator")
	}

	cs2.decideProposal(cs2.Height, cs2.Round+1)
	prop, propBlock := cs2.Proposal, cs2.ProposalBlock
	if prop == nil || propBlock == nil {
		t.Fatal("Failed to create proposal block with cs2")
	}

	incrementRound(cs2)

	<-timeoutChan

	log.Info("#### ONTO ROUND 4")
	/*
		Round4 (cs2, C) // B C // B C
	*/

	// now we're on a new round and not the proposer
	<-cs1.NewStepCh()
	// so set the proposal block
	cs1.mtx.Lock()
	cs1.Proposal, cs1.ProposalBlock = prop, propBlock
	cs1.mtx.Unlock()
	// and wait for timeout
	<-timeoutChan
	// go to prevote, prevote for locked block (not proposal)
	<-cs1.NewStepCh()
	validatePrevote(t, cs1, 0, privVals[0], cs1.LockedBlock.Hash())

	signAddVoteToFrom(types.VoteTypePrevote, cs1, cs2, propBlock.Hash(), propBlock.MakePartSet().Header())
	_, _, _ = <-cs1.NewStepCh(), <-timeoutChan, <-cs1.NewStepCh()
	validatePrecommit(t, cs1, 2, 0, privVals[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
}
Esempio n. 16
0
// 4 vals.
// we receive a final precommit after going into next round, but others might have gone to commit already!
func TestHalt1(t *testing.T) {
	css, privVals := simpleConsensusState(4)
	cs1, cs2, cs3, cs4 := css[0], css[1], css[2], css[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)

	// start round and wait for propose and prevote
	cs1.EnterNewRound(cs1.Height, 0, false)
	_, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh()

	theBlockHash := cs1.ProposalBlock.Hash()

	donePrecommit := make(chan struct{})
	go func() {
		<-cs1.NewStepCh()
		donePrecommit <- struct{}{}
	}()
	signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), cs3, cs4)
	<-donePrecommit

	// the proposed block should now be locked and our precommit added
	validatePrecommit(t, cs1, 0, 0, privVals[0], theBlockHash, theBlockHash)

	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())
	// 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

	incrementRound(cs2, cs3, cs4)

	// timeout to new round
	<-timeoutChan

	log.Info("### ONTO ROUND 2")
	/*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, privVals[0], cs1.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)

	if cs1.Height != 2 {
		t.Fatal("expected height to increment")
	}
}