Browse Source

proposer selection tests. closes #53

pull/164/head
Ethan Buchman 9 years ago
committed by Jae Kwon
parent
commit
209bcf905e
5 changed files with 217 additions and 56 deletions
  1. +15
    -4
      consensus/state.go
  2. +84
    -30
      consensus/state_test.go
  3. +31
    -1
      consensus/test.go
  4. +6
    -1
      types/validator_set.go
  5. +81
    -20
      types/validator_set_test.go

+ 15
- 4
consensus/state.go 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


+ 84
- 30
consensus/state_test.go 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 {


+ 31
- 1
consensus/test.go 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))
}


+ 6
- 1
types/validator_set.go View File

@ -35,9 +35,14 @@ func NewValidatorSet(vals []*Validator) *ValidatorSet {
validators[i] = val.Copy()
}
sort.Sort(ValidatorsByAddress(validators))
return &ValidatorSet{
vs := &ValidatorSet{
Validators: validators,
}
if vals != nil {
vs.IncrementAccum(1)
}
return vs
}
// TODO: mind the overflow when times and votingPower shares too large.


+ 81
- 20
types/validator_set_test.go View File

@ -49,37 +49,98 @@ func TestCopy(t *testing.T) {
func TestProposerSelection(t *testing.T) {
vset := NewValidatorSet([]*Validator{
&Validator{
Address: []byte("foo"),
PubKey: randPubKey(),
VotingPower: 1000,
Accum: 0,
},
&Validator{
Address: []byte("bar"),
PubKey: randPubKey(),
VotingPower: 300,
Accum: 0,
},
&Validator{
Address: []byte("baz"),
PubKey: randPubKey(),
VotingPower: 330,
Accum: 0,
},
newValidator([]byte("foo"), 1000),
newValidator([]byte("bar"), 300),
newValidator([]byte("baz"), 330),
})
proposers := []string{}
for i := 0; i < 100; i++ {
for i := 0; i < 99; i++ {
val := vset.Proposer()
proposers = append(proposers, string(val.Address))
vset.IncrementAccum(1)
}
expected := `bar foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo foo baz bar foo foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo`
expected := `foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo foo baz bar foo foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo`
if expected != strings.Join(proposers, " ") {
t.Errorf("Expected sequence of proposers was\n%v\nbut got \n%v", expected, strings.Join(proposers, " "))
}
}
func newValidator(address []byte, power int64) *Validator {
return &Validator{Address: address, VotingPower: power}
}
func TestProposerSelection2(t *testing.T) {
addr1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
addr2 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
addr3 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
// when all voting power is same, we go in order of addresses
val1, val2, val3 := newValidator(addr1, 100), newValidator(addr2, 100), newValidator(addr3, 100)
valList := []*Validator{val1, val2, val3}
vals := NewValidatorSet(valList)
for i := 0; i < len(valList)*5; i++ {
ii := i % len(valList)
prop := vals.Proposer()
if !bytes.Equal(prop.Address, valList[ii].Address) {
t.Fatalf("Expected %X. Got %X", valList[ii].Address, prop.Address)
}
vals.IncrementAccum(1)
}
// One validator has more than the others, but not enough to propose twice in a row
*val3 = *newValidator(addr3, 400)
vals = NewValidatorSet(valList)
prop := vals.Proposer()
if !bytes.Equal(prop.Address, addr3) {
t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.Proposer()
if !bytes.Equal(prop.Address, addr1) {
t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address)
}
// One validator has more than the others, and enough to be proposer twice in a row
*val3 = *newValidator(addr3, 401)
vals = NewValidatorSet(valList)
prop = vals.Proposer()
if !bytes.Equal(prop.Address, addr3) {
t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.Proposer()
if !bytes.Equal(prop.Address, addr3) {
t.Fatalf("Expected address with highest voting power to be second proposer. Got %X", prop.Address)
}
vals.IncrementAccum(1)
prop = vals.Proposer()
if !bytes.Equal(prop.Address, addr1) {
t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address)
}
// each validator should be the proposer a proportional number of times
val1, val2, val3 = newValidator(addr1, 4), newValidator(addr2, 5), newValidator(addr3, 3)
valList = []*Validator{val1, val2, val3}
propCount := make([]int, 3)
vals = NewValidatorSet(valList)
for i := 0; i < 120; i++ {
prop := vals.Proposer()
ii := prop.Address[19]
propCount[ii] += 1
vals.IncrementAccum(1)
}
if propCount[0] != 40 {
t.Fatalf("Expected prop count for validator with 4/12 of voting power to be 40/120. Got %d/120", propCount[0])
}
if propCount[1] != 50 {
t.Fatalf("Expected prop count for validator with 5/12 of voting power to be 50/120. Got %d/120", propCount[1])
}
if propCount[2] != 30 {
t.Fatalf("Expected prop count for validator with 3/12 of voting power to be 30/120. Got %d/120", propCount[2])
}
}
func BenchmarkValidatorSetCopy(b *testing.B) {
b.StopTimer()
vset := NewValidatorSet([]*Validator{})


Loading…
Cancel
Save