package state_test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" cryptoenc "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/mempool/mock" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) var ( chainID = "execution_chain" testPartSize uint32 = 65536 nTxsPerBlock = 10 ) func TestApplyBlock(t *testing.T) { app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() require.Nil(t, err) defer proxyApp.Stop() state, stateDB, _ := makeState(1, 1) blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()} state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block) require.Nil(t, err) assert.EqualValues(t, retainHeight, 1) // TODO check state and mempool assert.EqualValues(t, 1, state.Version.Consensus.App, "App version wasn't updated") } // TestBeginBlockValidators ensures we send absent validators list. func TestBeginBlockValidators(t *testing.T) { app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() require.Nil(t, err) defer proxyApp.Stop() state, stateDB, _ := makeState(2, 2) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts} var ( now = tmtime.Now() commitSig0 = types.NewCommitSigForBlock( []byte("Signature1"), state.Validators.Validators[0].Address, now) commitSig1 = types.NewCommitSigForBlock( []byte("Signature2"), state.Validators.Validators[1].Address, now) absentSig = types.NewCommitSigAbsent() ) testCases := []struct { desc string lastCommitSigs []types.CommitSig expectedAbsentValidators []int }{ {"none absent", []types.CommitSig{commitSig0, commitSig1}, []int{}}, {"one absent", []types.CommitSig{commitSig0, absentSig}, []int{1}}, {"multiple absent", []types.CommitSig{absentSig, absentSig}, []int{0, 1}}, } for _, tc := range testCases { lastCommit := types.NewCommit(1, 0, prevBlockID, tc.lastCommitSigs) // block for height 2 block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app receives a list of validators with a bool indicating if they signed ctr := 0 for i, v := range app.CommitVotes { if ctr < len(tc.expectedAbsentValidators) && tc.expectedAbsentValidators[ctr] == i { assert.False(t, v.SignedLastBlock) ctr++ } else { assert.True(t, v.SignedLastBlock) } } } } // TestBeginBlockByzantineValidators ensures we send byzantine validators list. func TestBeginBlockByzantineValidators(t *testing.T) { app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() require.Nil(t, err) defer proxyApp.Stop() state, stateDB, privVals := makeState(2, 12) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts} height1, val1 := int64(8), state.Validators.Validators[0].Address height2, val2 := int64(3), state.Validators.Validators[1].Address ev1 := types.NewMockDuplicateVoteEvidenceWithValidator(height1, time.Now(), privVals[val1.String()], chainID) ev2 := types.NewMockDuplicateVoteEvidenceWithValidator(height2, time.Now(), privVals[val2.String()], chainID) now := tmtime.Now() valSet := state.Validators testCases := []struct { desc string evidence []types.Evidence expectedByzantineValidators []abci.Evidence }{ {"none byzantine", []types.Evidence{}, []abci.Evidence{}}, {"one byzantine", []types.Evidence{ev1}, []abci.Evidence{types.TM2PB.Evidence(ev1, valSet, now)}}, {"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{ types.TM2PB.Evidence(ev1, valSet, now), types.TM2PB.Evidence(ev2, valSet, now)}}, } var ( commitSig0 = types.NewCommitSigForBlock( []byte("Signature1"), state.Validators.Validators[0].Address, now) commitSig1 = types.NewCommitSigForBlock( []byte("Signature2"), state.Validators.Validators[1].Address, now) ) commitSigs := []types.CommitSig{commitSig0, commitSig1} lastCommit := types.NewCommit(9, 0, prevBlockID, commitSigs) for _, tc := range testCases { block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) block.Time = now block.Evidence.Evidence = tc.evidence _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app must receive an index of the byzantine validator assert.Equal(t, tc.expectedByzantineValidators, app.ByzantineValidators, tc.desc) } } func TestValidateValidatorUpdates(t *testing.T) { pubkey1 := ed25519.GenPrivKey().PubKey() pubkey2 := ed25519.GenPrivKey().PubKey() pk1, err := cryptoenc.PubKeyToProto(pubkey1) assert.NoError(t, err) pk2, err := cryptoenc.PubKeyToProto(pubkey2) assert.NoError(t, err) defaultValidatorParams := tmproto.ValidatorParams{PubKeyTypes: []string{types.ABCIPubKeyTypeEd25519}} testCases := []struct { name string abciUpdates []abci.ValidatorUpdate validatorParams tmproto.ValidatorParams shouldErr bool }{ { "adding a validator is OK", []abci.ValidatorUpdate{{PubKey: pk2, Power: 20}}, defaultValidatorParams, false, }, { "updating a validator is OK", []abci.ValidatorUpdate{{PubKey: pk1, Power: 20}}, defaultValidatorParams, false, }, { "removing a validator is OK", []abci.ValidatorUpdate{{PubKey: pk2, Power: 0}}, defaultValidatorParams, false, }, { "adding a validator with negative power results in error", []abci.ValidatorUpdate{{PubKey: pk2, Power: -100}}, defaultValidatorParams, true, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams) if tc.shouldErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestUpdateValidators(t *testing.T) { pubkey1 := ed25519.GenPrivKey().PubKey() val1 := types.NewValidator(pubkey1, 10) pubkey2 := ed25519.GenPrivKey().PubKey() val2 := types.NewValidator(pubkey2, 20) pk, err := cryptoenc.PubKeyToProto(pubkey1) require.NoError(t, err) pk2, err := cryptoenc.PubKeyToProto(pubkey2) require.NoError(t, err) testCases := []struct { name string currentSet *types.ValidatorSet abciUpdates []abci.ValidatorUpdate resultingSet *types.ValidatorSet shouldErr bool }{ { "adding a validator is OK", types.NewValidatorSet([]*types.Validator{val1}), []abci.ValidatorUpdate{{PubKey: pk2, Power: 20}}, types.NewValidatorSet([]*types.Validator{val1, val2}), false, }, { "updating a validator is OK", types.NewValidatorSet([]*types.Validator{val1}), []abci.ValidatorUpdate{{PubKey: pk, Power: 20}}, types.NewValidatorSet([]*types.Validator{types.NewValidator(pubkey1, 20)}), false, }, { "removing a validator is OK", types.NewValidatorSet([]*types.Validator{val1, val2}), []abci.ValidatorUpdate{{PubKey: pk2, Power: 0}}, types.NewValidatorSet([]*types.Validator{val1}), false, }, { "removing a non-existing validator results in error", types.NewValidatorSet([]*types.Validator{val1}), []abci.ValidatorUpdate{{PubKey: pk2, Power: 0}}, types.NewValidatorSet([]*types.Validator{val1}), true, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { updates, err := types.PB2TM.ValidatorUpdates(tc.abciUpdates) assert.NoError(t, err) err = tc.currentSet.UpdateWithChangeSet(updates) if tc.shouldErr { assert.Error(t, err) } else { assert.NoError(t, err) require.Equal(t, tc.resultingSet.Size(), tc.currentSet.Size()) assert.Equal(t, tc.resultingSet.TotalVotingPower(), tc.currentSet.TotalVotingPower()) assert.Equal(t, tc.resultingSet.Validators[0].Address, tc.currentSet.Validators[0].Address) if tc.resultingSet.Size() > 1 { assert.Equal(t, tc.resultingSet.Validators[1].Address, tc.currentSet.Validators[1].Address) } } }) } } // TestEndBlockValidatorUpdates ensures we update validator set and send an event. func TestEndBlockValidatorUpdates(t *testing.T) { app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() require.Nil(t, err) defer proxyApp.Stop() state, stateDB, _ := makeState(1, 1) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}, ) eventBus := types.NewEventBus() err = eventBus.Start() require.NoError(t, err) defer eventBus.Stop() blockExec.SetEventBus(eventBus) updatesSub, err := eventBus.Subscribe( context.Background(), "TestEndBlockValidatorUpdates", types.EventQueryValidatorSetUpdates, ) require.NoError(t, err) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()} pubkey := ed25519.GenPrivKey().PubKey() pk, err := cryptoenc.PubKeyToProto(pubkey) require.NoError(t, err) app.ValidatorUpdates = []abci.ValidatorUpdate{ {PubKey: pk, Power: 10}, } state, _, err = blockExec.ApplyBlock(state, blockID, block) require.Nil(t, err) // test new validator was added to NextValidators if assert.Equal(t, state.Validators.Size()+1, state.NextValidators.Size()) { idx, _ := state.NextValidators.GetByAddress(pubkey.Address()) if idx < 0 { t.Fatalf("can't find address %v in the set %v", pubkey.Address(), state.NextValidators) } } // test we threw an event select { case msg := <-updatesSub.Out(): event, ok := msg.Data().(types.EventDataValidatorSetUpdates) require.True(t, ok, "Expected event of type EventDataValidatorSetUpdates, got %T", msg.Data()) if assert.NotEmpty(t, event.ValidatorUpdates) { assert.Equal(t, pubkey, event.ValidatorUpdates[0].PubKey) assert.EqualValues(t, 10, event.ValidatorUpdates[0].VotingPower) } case <-updatesSub.Cancelled(): t.Fatalf("updatesSub was cancelled (reason: %v)", updatesSub.Err()) case <-time.After(1 * time.Second): t.Fatal("Did not receive EventValidatorSetUpdates within 1 sec.") } } // TestEndBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that // would result in empty set causes no panic, an error is raised and NextValidators is not updated func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() require.Nil(t, err) defer proxyApp.Stop() state, stateDB, _ := makeState(1, 1) blockExec := sm.NewBlockExecutor( stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}, ) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()} vp, err := cryptoenc.PubKeyToProto(state.Validators.Validators[0].PubKey) require.NoError(t, err) // Remove the only validator app.ValidatorUpdates = []abci.ValidatorUpdate{ {PubKey: vp, Power: 0}, } assert.NotPanics(t, func() { state, _, err = blockExec.ApplyBlock(state, blockID, block) }) assert.NotNil(t, err) assert.NotEmpty(t, state.NextValidators.Validators) }