Skip to content

Commit

Permalink
test(consensus): TestStateProposerSelectionBetweenRoundsAndHeights
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek committed Sep 4, 2024
1 parent 9833e0e commit f67cf4a
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
6 changes: 6 additions & 0 deletions internal/consensus/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ func incrementRound(vss ...*validatorStub) {
}
}

func resetRound(vss ...*validatorStub) {
for _, vs := range vss {
vs.Round = 0
}
}

func sortVValidatorStubsByPower(ctx context.Context, t *testing.T, vss []*validatorStub) []*validatorStub {
t.Helper()
sort.Slice(vss, func(i, j int) bool {
Expand Down
115 changes: 115 additions & 0 deletions internal/consensus/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/sasha-s/go-deadlock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -150,6 +151,120 @@ func TestStateProposerSelection2(t *testing.T) {

}

// Given `nVals` validators,
// When consensus runs with multiple rounds and heights,
// Then the proposer should be selected in a round-robin fashion.
func TestStateProposerSelectionBetweenRoundsAndHeights(t *testing.T) {
// TODO: Enable deadlock detection and check why it complains
deadlock.Opts.Disable = true
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

const nVals = 5
const maxHeight = 5

// SETUP TEST
cfg := configSetup(t)
cfg.Consensus.DontAutoPropose = true
cfg.Consensus.CreateEmptyBlocksInterval = 0

cp := factory.ConsensusParams()
cp.Timeout = types.TimeoutParams{
Propose: 10 * time.Millisecond,
ProposeDelta: 1,
Vote: 10 * time.Millisecond,
VoteDelta: 1,
}

gen := consensusNetGen{
cfg: cfg,
nPeers: nVals,
nVals: nVals,
appFunc: newKVStoreFunc(t),
consensusParams: cp,
}
css, genesis, _, _ := gen.generate(ctx, t)

cs1 := css[0]
expectedValidatorSet := cs1.GetStateData().Validators.Copy()

vss := make([]*validatorStub, len(css))
for i, cs := range css {
vss[i] = newValidatorStub(cs.privValidator, int32(i), genesis.InitialHeight)
}

// Get some useful statistics into `proposers`
type proposer struct {
height int64
round int32
proposer *types.Validator
}
proposers := []proposer{}

stateData := cs1.GetStateData()
height, round := stateData.Height, stateData.Round
newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound)
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)

// START TEST NETWORK

// Genesis block

// start the machine; note height should be equal to InitialHeight here,
// so we don't need to increment it
startTestRound(ctx, cs1, height, round)
ensureNewRound(t, newRoundCh, height, 0)

proposers = append(proposers, proposer{height, round, cs1.GetRoundState().Validators.GetProposer()})

ensureNewProposal(t, proposalCh, height, round)

blockID := cs1.GetStateData().Proposal.BlockID.Copy()
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, cfg.ChainID(), blockID, vss[1:]...)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), blockID, vss[1:]...)

height++
ensureNewRound(t, newRoundCh, height, 0)

for ; height <= maxHeight; height++ {
incrementHeight(vss[1:]...)
resetRound(vss[1:]...)
for round := int32(0); round < int32(height); round++ {
proposers = append(proposers, proposer{height, round, cs1.GetRoundState().Validators.GetProposer()})
t.Logf("height %d, round %d, proposer %x", height, round, proposers[len(proposers)-1].proposer.ProTxHash[:6])

// cs1 is automatically proposing
if !cs1.GetRoundState().Validators.GetProposer().ProTxHash.Equal(cs1.privValidator.ProTxHash) {
createSignSendProposal(ctx, t, css, vss, cfg.ChainID(), nil)
}
blockID := ensureNewProposal(t, proposalCh, height, round)

assert.False(t, blockID.IsNil())
assert.EqualValues(t, height, cs1.GetStateData().Height)
assert.EqualValues(t, round, cs1.GetStateData().Round)

if round == int32(height)-1 { // we commit
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, cfg.ChainID(), blockID, vss[1:]...)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), blockID, vss[1:]...)
ensureNewRound(t, newRoundCh, height+1, 0)
} else { // we vote nil
signAddVotes(ctx, t, cs1, tmproto.PrevoteType, cfg.ChainID(), types.BlockID{}, vss[1:]...)
signAddVotes(ctx, t, cs1, tmproto.PrecommitType, cfg.ChainID(), types.BlockID{}, vss[1:]...)
ensureNewRound(t, newRoundCh, height, round+1)
incrementRound(vss[1:]...)
}
}
}

// ensure correct order of voting
for i, p := range proposers {
expectedProTx := expectedValidatorSet.Validators[(i)%len(expectedValidatorSet.Validators)].ProTxHash
assert.Equal(t, expectedProTx, p.proposer.ProTxHash)
t.Logf("height %d, round %d, proposer %x", p.height, p.round, p.proposer.ProTxHash[:6])
}

}

// a non-validator should timeout into the prevote round
func TestStateEnterProposeNoPrivValidator(t *testing.T) {
config := configSetup(t)
Expand Down

0 comments on commit f67cf4a

Please sign in to comment.