package consensus import ( "testing" "time" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" db_ "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/state" ) func randAccountDetail(id uint64, status byte) (*state.AccountDetail, *state.PrivAccount) { privAccount := state.GenPrivAccount() privAccount.Id = id account := privAccount.Account return &state.AccountDetail{ Account: account, Sequence: RandUInt(), Balance: 1000, Status: status, }, privAccount } // The first numValidators accounts are validators. func randGenesisState(numAccounts int, numValidators int) (*state.State, []*state.PrivAccount) { db := db_.NewMemDB() accountDetails := make([]*state.AccountDetail, numAccounts) privAccounts := make([]*state.PrivAccount, numAccounts) for i := 0; i < numAccounts; i++ { if i < numValidators { accountDetails[i], privAccounts[i] = randAccountDetail(uint64(i), state.AccountStatusBonded) } else { accountDetails[i], privAccounts[i] = randAccountDetail(uint64(i), state.AccountStatusNominal) } } s0 := state.GenesisState(db, time.Now(), accountDetails) s0.Save() return s0, privAccounts } func makeConsensusState() (*ConsensusState, []*state.PrivAccount) { state, privAccounts := randGenesisState(20, 10) blockStore := NewBlockStore(db_.NewMemDB()) mempool := mempool.NewMempool(state) cs := NewConsensusState(state, blockStore, mempool) return cs, privAccounts } func assertPanics(t *testing.T, msg string, f func()) { defer func() { if err := recover(); err == nil { t.Error("Should have panic'd, but didn't. %v", msg) } }() f() } //----------------------------------------------------------------------------- func TestSetupRound(t *testing.T) { cs, privAccounts := makeConsensusState() // Add a vote, precommit, and commit by val0. voteTypes := []byte{VoteTypePrevote, VoteTypePrecommit, VoteTypeCommit} for _, voteType := range voteTypes { vote := &Vote{Height: 1, Round: 0, Type: voteType} // nil vote privAccounts[0].Sign(vote) cs.AddVote(vote) } // Ensure that vote appears in RoundState. rs0 := cs.GetRoundState() if vote := rs0.Prevotes.Get(0); vote == nil || vote.Type != VoteTypePrevote { t.Errorf("Expected to find prevote but got %v", vote) } if vote := rs0.Precommits.Get(0); vote == nil || vote.Type != VoteTypePrecommit { t.Errorf("Expected to find precommit but got %v", vote) } if vote := rs0.Commits.Get(0); vote == nil || vote.Type != VoteTypeCommit { t.Errorf("Expected to find commit but got %v", vote) } // Setup round 1 (next round) cs.SetupRound(1) // Now the commit should be copied over to prevotes and precommits. rs1 := cs.GetRoundState() if vote := rs1.Prevotes.Get(0); vote == nil || vote.Type != VoteTypeCommit { t.Errorf("Expected to find commit but got %v", vote) } if vote := rs1.Precommits.Get(0); vote == nil || vote.Type != VoteTypeCommit { t.Errorf("Expected to find commit but got %v", vote) } if vote := rs1.Commits.Get(0); vote == nil || vote.Type != VoteTypeCommit { t.Errorf("Expected to find commit but got %v", vote) } // Setup round 1 (should fail) assertPanics(t, "Round did not increment", func() { cs.SetupRound(1) }) } func TestRunActionProposeNoPrivValidator(t *testing.T) { cs, _ := makeConsensusState() cs.RunActionPropose(1, 0) rs := cs.GetRoundState() if rs.Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") } } func TestRunActionPropose(t *testing.T) { cs, privAccounts := makeConsensusState() priv := NewPrivValidator(db_.NewMemDB(), privAccounts[0]) cs.SetPrivValidator(priv) cs.RunActionPropose(1, 0) rs := cs.GetRoundState() // Check that Proposal, ProposalBlock, ProposalBlockPartSet 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.ProposalBlockPartSet.Total() == 0 { t.Error("rs.ProposalBlockPartSet should be set") } } func checkRoundState(t *testing.T, cs *ConsensusState, height uint32, round uint16, step RoundStep) { rs := cs.GetRoundState() if rs.Height != height { t.Errorf("cs.RoundState.Height should be %v, got %v", height, rs.Height) } if rs.Round != round { t.Errorf("cs.RoundState.Round should be %v, got %v", round, rs.Round) } if rs.Step != step { t.Errorf("cs.RoundState.Step should be %v, got %v", step, rs.Step) } } func TestRunActionPrecommitCommitFinalize(t *testing.T) { cs, privAccounts := makeConsensusState() priv := NewPrivValidator(db_.NewMemDB(), privAccounts[0]) cs.SetPrivValidator(priv) vote := cs.RunActionPrecommit(1, 0) if vote != nil { t.Errorf("RunActionPrecommit should return nil without a proposal") } cs.RunActionPropose(1, 0) // Test RunActionPrecommit failures: assertPanics(t, "Wrong height ", func() { cs.RunActionPrecommit(2, 0) }) assertPanics(t, "Wrong round", func() { cs.RunActionPrecommit(1, 1) }) vote = cs.RunActionPrecommit(1, 0) if vote != nil { t.Errorf("RunActionPrecommit should return nil, not enough prevotes") } // Add at least +2/3 prevotes. for i := 0; i < 7; i++ { vote := &Vote{ Height: 1, Round: 0, Type: VoteTypePrevote, BlockHash: cs.ProposalBlock.Hash(), } privAccounts[i].Sign(vote) cs.AddVote(vote) } // Test RunActionPrecommit success: vote = cs.RunActionPrecommit(1, 0) if vote == nil { t.Errorf("RunActionPrecommit should have succeeded") } checkRoundState(t, cs, 1, 0, RoundStepPrecommit) // Test RunActionCommit failures: assertPanics(t, "Wrong height ", func() { cs.RunActionCommit(2, 0) }) assertPanics(t, "Wrong round", func() { cs.RunActionCommit(1, 1) }) // Add at least +2/3 precommits. for i := 0; i < 7; i++ { vote := &Vote{ Height: 1, Round: 0, Type: VoteTypePrecommit, BlockHash: cs.ProposalBlock.Hash(), } privAccounts[i].Sign(vote) cs.AddVote(vote) } // Test RunActionCommit success: vote = cs.RunActionCommit(1, 0) if vote == nil { t.Errorf("RunActionCommit should have succeeded") } checkRoundState(t, cs, 1, 0, RoundStepCommit) // cs.CommitTime should still be zero if !cs.CommitTime.IsZero() { t.Errorf("Expected CommitTime to yet be zero") } // Add at least +2/3 commits. for i := 0; i < 7; i++ { vote := &Vote{ Height: 1, Round: uint16(i), // Doesn't matter what round Type: VoteTypeCommit, BlockHash: cs.ProposalBlock.Hash(), } privAccounts[i].Sign(vote) cs.AddVote(vote) } // Test RunActionCommitWait: cs.RunActionCommitWait(1, 0) if cs.CommitTime.IsZero() { t.Errorf("Expected CommitTime to have been set") } checkRoundState(t, cs, 1, 0, RoundStepCommitWait) // Test RunActionFinalize: cs.RunActionFinalize(1, 0) checkRoundState(t, cs, 2, 0, RoundStepStart) }