2015-09-22 21:12:34 -04:00
package consensus
import (
"bytes"
"fmt"
"testing"
"time"
_ "github.com/tendermint/tendermint/config/tendermint_test"
"github.com/tendermint/tendermint/events"
"github.com/tendermint/tendermint/types"
)
/ *
ProposeSuite
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
FullRoundSuite
x * TestFullRound1 - 1 val , full successful round
x * TestFullRoundNil - 1 val , full round of nil
x * TestFullRound2 - 2 vals , both required for fuill round
LockSuite
x * TestLockNoPOL - 2 vals , 4 rounds . one val locked , precommits nil every round except first .
x * TestLockPOLRelock - 4 vals , one precommits , other 3 polka at next round , so we unlock and precomit the polka
x * TestLockPOLUnlock - 4 vals , one precommits , other 3 polka nil at next round , so we unlock and precomit nil
x * TestLockPOLSafety1 - 4 vals . We shouldn ' t change lock based on polka at earlier round
x * TestLockPOLSafety2 - 4 vals . After unlocking , we shouldn ' t relock based on polka at earlier round
* TestNetworkLock - once + 1 / 3 precommits , network should be locked
* TestNetworkLockPOL - once + 1 / 3 precommits , the block with more recent polka is committed
SlashingSuite
x * TestSlashingPrevotes - a validator prevoting twice in a round gets slashed
x * TestSlashingPrecommits - a validator precomitting twice in a round gets slashed
CatchupSuite
* TestCatchup - if we might be behind and we ' ve seen any 2 / 3 prevotes , round skip to new round , precommit , or prevote
HaltSuite
x * TestHalt1 - if we see + 2 / 3 precommits after timing out into new round , we should still commit
* /
//----------------------------------------------------------------------------------------------------
// ProposeSuite
func init ( ) {
fmt . Println ( "" )
timeoutPropose = 1000 * time . Millisecond
}
// a non-validator should timeout into the prevote round
func TestEnterProposeNoPrivValidator ( t * testing . T ) {
css , _ := simpleConsensusState ( 1 )
cs := css [ 0 ]
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 )
// starts a go routine for EnterPropose
cs . EnterNewRound ( cs . Height , 0 , false )
// go to prevote
<- cs . NewStepCh ( )
// if we're not a validator, EnterPropose should timeout
select {
case rs := <- cs . NewStepCh ( ) :
log . Info ( rs . String ( ) )
t . Fatal ( "Expected EnterPropose to timeout" )
case <- timeoutChan :
rs := cs . GetRoundState ( )
if rs . Proposal != nil {
t . Error ( "Expected to make no proposal, since no privValidator" )
}
break
}
}
// a validator should not timeout of the prevote round (TODO: unless the block is really big!)
func TestEnterPropose ( t * testing . T ) {
css , _ := simpleConsensusState ( 1 )
cs := css [ 0 ]
timeoutChan := make ( chan struct { } )
evsw := events . NewEventSwitch ( )
evsw . OnStart ( )
evsw . AddListenerForEvent ( "tester" , types . EventStringTimeoutPropose ( ) , func ( data types . EventData ) {
timeoutChan <- struct { } { }
} )
cs . SetFireable ( evsw )
// starts a go routine for EnterPropose
cs . EnterNewRound ( cs . Height , 0 , false )
// go to prevote
<- cs . NewStepCh ( )
// if we are a validator, we expect it not to timeout
select {
case <- cs . NewStepCh ( ) :
rs := cs . GetRoundState ( )
// Check that Proposal, ProposalBlock, ProposalBlockParts are set.
if rs . Proposal == nil {
t . Error ( "rs.Proposal should be set" )
}
if rs . ProposalBlock == nil {
t . Error ( "rs.ProposalBlock should be set" )
}
if rs . ProposalBlockParts . Total ( ) == 0 {
t . Error ( "rs.ProposalBlockParts should be set" )
}
break
case <- timeoutChan :
t . Fatal ( "Expected EnterPropose not to timeout" )
}
}
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 ( ) )
}
//----------------------------------------------------------------------------------------------------
// FulLRoundSuite
// propose, prevote, and precommit a block
func TestFullRound1 ( t * testing . T ) {
css , privVals := simpleConsensusState ( 1 )
cs := css [ 0 ]
// starts a go routine for EnterPropose
cs . EnterNewRound ( cs . Height , 0 , false )
// wait to finish propose and prevote
_ , _ = <- cs . NewStepCh ( ) , <- cs . NewStepCh ( )
// we should now be in precommit
// verify our prevote is there
cs . mtx . Lock ( )
propBlockHash := cs . ProposalBlock . Hash ( )
cs . mtx . Unlock ( )
// the proposed block should be prevoted, precommitted, and locked
validatePrevoteAndPrecommit ( t , cs , 0 , 0 , privVals [ 0 ] , propBlockHash , propBlockHash , nil )
}
// nil is proposed, so prevote and precommit nil
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 )
// 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 ( )
// should prevote and precommit nil
validatePrevoteAndPrecommit ( t , cs , 0 , 0 , privVals [ 0 ] , nil , nil , nil )
}
// run through propose, prevote, precommit commit with two validators
// where the first validator has to wait for votes from the second
func TestFullRound2 ( t * testing . T ) {
css , privVals := simpleConsensusState ( 2 )
cs1 , cs2 := css [ 0 ] , css [ 1 ]
cs1 . newStepCh = make ( chan * RoundState ) // so it blocks
cs2 . newStepCh = make ( chan * RoundState ) // so it blocks
// 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
ensureNoNewStep ( t , cs1 )
propBlockHash , propPartsHeader := cs1 . ProposalBlock . Hash ( ) , cs1 . ProposalBlockParts . Header ( )
// prevote arrives from cs2:
signAddVoteToFrom ( types . VoteTypePrevote , cs1 , cs2 , propBlockHash , propPartsHeader )
// wait to finish precommit
<- cs1 . NewStepCh ( )
// the proposed block should now be locked and our precommit added
validatePrecommit ( t , cs1 , 0 , 0 , privVals [ 0 ] , propBlockHash , propBlockHash )
// we should now be stuck in limbo forever, waiting for more precommits
ensureNoNewStep ( t , cs1 )
// precommit arrives from cs2:
signAddVoteToFrom ( types . VoteTypePrecommit , cs1 , cs2 , propBlockHash , propPartsHeader )
// wait to finish commit, propose in next height
_ , rs := <- cs1 . NewStepCh ( ) , <- cs1 . NewStepCh ( )
if rs . Height != 2 {
t . Fatal ( "Expected height to increment" )
}
}
//------------------------------------------------------------------------------------------
// LockSuite
// 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
}
// 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" )
}
}
// 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
}
// 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 )
}
// 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" )
}
}
//------------------------------------------------------------------------------------------
// SlashingSuite
func TestSlashingPrevotes ( t * testing . T ) {
css , _ := simpleConsensusState ( 2 )
cs1 , cs2 := css [ 0 ] , css [ 1 ]
cs1 . newStepCh = make ( chan * RoundState ) // so it blocks
// 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
// 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 ( ) )
// pass prevote wait
<- cs1 . NewStepCh ( )
// 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 ( ) )
2015-11-01 11:34:08 -08:00
// XXX: Check for existence of Dupeout info
2015-09-22 21:12:34 -04:00
}
func TestSlashingPrecommits ( t * testing . T ) {
css , _ := simpleConsensusState ( 2 )
cs1 , cs2 := css [ 0 ] , css [ 1 ]
cs1 . newStepCh = make ( chan * RoundState ) // so it blocks
// start round and wait for propose and prevote
cs1 . EnterNewRound ( cs1 . Height , 0 , false )
_ , _ = <- cs1 . NewStepCh ( ) , <- cs1 . NewStepCh ( )
// add prevote from cs2
signAddVoteToFrom ( types . VoteTypePrevote , cs1 , cs2 , cs1 . ProposalBlock . Hash ( ) , cs1 . ProposalBlockParts . Header ( ) )
// wait to finish precommit
<- cs1 . NewStepCh ( )
// 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 . VoteTypePrecommit , cs1 , cs2 , hash , cs1 . ProposalBlockParts . Header ( ) )
// pass prevote wait
<- cs1 . NewStepCh ( )
// 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 ( ) )
2015-11-01 11:34:08 -08:00
// XXX: Check for existence of Dupeout info
2015-09-22 21:12:34 -04:00
}
//------------------------------------------------------------------------------------------
// CatchupSuite
//------------------------------------------------------------------------------------------
// HaltSuite
// 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" )
}
}