proposer selection tests. closes #53

This commit is contained in:
Ethan Buchman
2015-10-28 19:49:35 +02:00
committed by Jae Kwon
parent d69b5c5ab6
commit 209bcf905e
5 changed files with 217 additions and 56 deletions

View File

@@ -166,13 +166,16 @@ type ConsensusState struct {
evsw events.Fireable
evc *events.EventCache // set in stageBlock and passed into state
decideProposalFunc func(cs *ConsensusState, height int, round int)
}
func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReactor *mempl.MempoolReactor) *ConsensusState {
cs := &ConsensusState{
blockStore: blockStore,
mempoolReactor: mempoolReactor,
newStepCh: make(chan *RoundState, 10),
blockStore: blockStore,
mempoolReactor: mempoolReactor,
newStepCh: make(chan *RoundState, 10),
decideProposalFunc: decideProposal,
}
cs.updateToState(state)
// Don't call scheduleRound0 yet.
@@ -182,6 +185,10 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto
return cs
}
func (cs *ConsensusState) SetDecideProposalFunc(f func(cs *ConsensusState, height int, round int)) {
cs.decideProposalFunc = f
}
// Reconstruct LastCommit from SeenValidation, which we saved along with the block,
// (which happens even before saving the state)
func (cs *ConsensusState) reconstructLastCommit(state *sm.State) {
@@ -418,8 +425,12 @@ func (cs *ConsensusState) EnterPropose(height int, round int) {
}
}
func (cs *ConsensusState) decideProposal(height, round int) {
cs.decideProposalFunc(cs, height, round)
}
// Decides on the next proposal and sets them onto cs.Proposal*
func (cs *ConsensusState) decideProposal(height int, round int) {
func decideProposal(cs *ConsensusState, height, round int) {
var block *types.Block
var blockParts *types.PartSet

View File

@@ -14,6 +14,7 @@ import (
/*
ProposeSuite
x * TestProposerSelection - round robin ordering
x * TestEnterProposeNoValidator - timeout into prevote round
x * TestEnterPropose - finish propose without timing out (we have the proposal)
x * TestBadProposal - 2 vals, bad proposal (bad block state hash), should prevote and precommit nil
@@ -44,7 +45,77 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh
func init() {
fmt.Println("")
timeoutPropose = 1000 * time.Millisecond
timeoutPropose = 500 * time.Millisecond
}
func TestProposerSelection(t *testing.T) {
css, _ := simpleConsensusState(3) // test needs more work for more than 3 validators
cs1 := css[0]
cs1.newStepCh = make(chan *RoundState) // so it blocks
cs1.SetDecideProposalFunc(nilProposal)
cs1.EnterNewRound(cs1.Height, 0, false)
// everyone just votes nil. we get a new proposer each round
for i := 0; i < len(css); i++ {
if i == len(css)-1 {
// reset cs1's decideProposal function for later
cs1.SetDecideProposalFunc(decideProposal)
}
prop := cs1.Validators.Proposer()
if !bytes.Equal(prop.Address, css[i].privValidator.Address) {
t.Fatalf("expected proposer to be validator %d. Got %X", i, prop.Address)
}
nilRound(t, 0, cs1, css[1:]...)
incrementRound(css[1:]...)
}
// now we should be back at first validator.
// lets commit a block and ensure proposer for the next height is correct
height := cs1.Height
prop := cs1.Validators.Proposer()
if !bytes.Equal(prop.Address, cs1.privValidator.Address) {
t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address)
}
signAddVoteToFromMany(types.VoteTypePrevote, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), css[1:]...)
<-cs1.NewStepCh() // prevotes
signAddVoteToFromMany(types.VoteTypePrecommit, cs1, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), css[1:]...)
<-cs1.NewStepCh() //
<-cs1.NewStepCh() // go to next round
if cs1.Height != height+1 {
t.Fatal("Expected height to increment. Got", cs1.Height)
}
prop = cs1.Validators.Proposer()
if !bytes.Equal(prop.Address, css[1].privValidator.Address) {
t.Fatalf("expected proposer to be validator %d. Got %X", 1, prop.Address)
}
// Now let's do it all again, but starting from round 2 instead of 0
css, _ = simpleConsensusState(3) // test needs more work for more than 3 validators
cs1 = css[0]
cs1.newStepCh = make(chan *RoundState) // so it blocks
cs1.SetDecideProposalFunc(nilProposal)
// this time we jump in at round 2
incrementRound(css[1:]...)
incrementRound(css[1:]...)
cs1.EnterNewRound(cs1.Height, 2, false)
// everyone just votes nil. we get a new proposer each round
for i := 0; i < len(css); i++ {
prop := cs1.Validators.Proposer()
if !bytes.Equal(prop.Address, css[(i+2)%len(css)].privValidator.Address) {
t.Fatalf("expected proposer to be validator %d. Got %X", (i+2)%len(css), prop.Address)
}
nilRound(t, 2, cs1, css[1:]...)
incrementRound(css[1:]...)
}
}
// a non-validator should timeout into the prevote round
@@ -175,7 +246,7 @@ func TestBadProposal(t *testing.T) {
}
//----------------------------------------------------------------------------------------------------
// FulLRoundSuite
// FullRoundSuite
// propose, prevote, and precommit a block
func TestFullRound1(t *testing.T) {
@@ -202,23 +273,14 @@ func TestFullRoundNil(t *testing.T) {
css, privVals := simpleConsensusState(1)
cs := css[0]
cs.newStepCh = make(chan *RoundState) // so it blocks
cs.SetPrivValidator(nil)
timeoutChan := make(chan struct{})
evsw := events.NewEventSwitch()
evsw.OnStart()
evsw.AddListenerForEvent("tester", types.EventStringTimeoutPropose(), func(data types.EventData) {
timeoutChan <- struct{}{}
})
cs.SetFireable(evsw)
cs.SetDecideProposalFunc(nilProposal)
// starts a go routine for EnterPropose
cs.EnterNewRound(cs.Height, 0, false)
// wait to finish propose (we should time out)
<-cs.NewStepCh()
cs.SetPrivValidator(privVals[0]) // this might be a race condition (uses the mutex that EnterPropose has just released and EnterPrevote is about to grab)
<-timeoutChan
// wait to finish prevote
<-cs.NewStepCh()
@@ -328,12 +390,10 @@ func TestLockNoPOL(t *testing.T) {
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)
// 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")
}
// wait to finish prevote
@@ -368,10 +428,11 @@ func TestLockNoPOL(t *testing.T) {
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")
<-cs1.newStepCh
// 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)
}
// go to prevote, prevote for locked block
@@ -386,14 +447,7 @@ func TestLockNoPOL(t *testing.T) {
<-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")
}
// before we time out into new round, set next proposal block
cs2.decideProposal(cs2.Height, cs2.Round+1)
prop, propBlock := cs2.Proposal, cs2.ProposalBlock
if prop == nil || propBlock == nil {

View File

@@ -18,6 +18,36 @@ import (
//-------------------------------------------------------------------------------
// utils
func nilProposal(cs *ConsensusState, height, round int) {
// Make proposal
proposal := types.NewProposal(height, round, types.PartSetHeader{}, -1)
err := cs.privValidator.SignProposal(cs.state.ChainID, proposal)
if err == nil {
log.Notice("Signed and set proposal", "height", height, "round", round, "proposal", proposal)
// Set fields
cs.Proposal = proposal
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
} else {
log.Warn("EnterPropose: Error signing proposal", "height", height, "round", round, "error", err)
}
}
func nilRound(t *testing.T, startRound int, cs1 *ConsensusState, css ...*ConsensusState) {
round := cs1.Round
if round == startRound {
_, _ = <-cs1.NewStepCh(), <-cs1.NewStepCh()
}
signAddVoteToFromMany(types.VoteTypePrevote, cs1, nil, cs1.ProposalBlockParts.Header(), css...)
<-cs1.NewStepCh() // prevotes
signAddVoteToFromMany(types.VoteTypePrecommit, cs1, nil, cs1.ProposalBlockParts.Header(), css...)
<-cs1.NewStepCh() //
<-cs1.NewStepCh() // go to next round
if cs1.Round != round+1 {
t.Fatal("Expected round to increment. Got", cs1.Round)
}
}
func changeProposer(t *testing.T, perspectiveOf, newProposer *ConsensusState) *types.Block {
_, v1 := perspectiveOf.Validators.GetByAddress(perspectiveOf.privValidator.Address)
v1.Accum, v1.VotingPower = 0, 0
@@ -62,7 +92,7 @@ func addVoteToFrom(to, from *ConsensusState, vote *types.Vote) {
if _, ok := err.(*types.ErrVoteConflictingSignature); ok {
// let it fly
} else if !added {
panic("Failed to add vote")
panic(fmt.Sprintln("Failed to add vote. Err:", err))
} else if err != nil {
panic(fmt.Sprintln("Failed to add vote:", err))
}