package state_test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" abcimocks "github.com/tendermint/tendermint/abci/types/mocks" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/proxy" "github.com/tendermint/tendermint/internal/pubsub" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/mocks" sf "github.com/tendermint/tendermint/internal/state/test/factory" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" ) var ( chainID = "execution_chain" testPartSize uint32 = 65536 ) func TestApplyBlock(t *testing.T) { app := &testApp{} logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) ctx, cancel := context.WithCancel(context.Background()) defer cancel() require.NoError(t, proxyApp.Start(ctx)) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) mp := &mpmocks.Mempool{} mp.On("Lock").Return() mp.On("Unlock").Return() mp.On("FlushAppConn", mock.Anything).Return(nil) mp.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) blockExec := sm.NewBlockExecutor(stateStore, logger, proxyApp, mp, sm.EmptyEvidencePool{}, blockStore, eventBus) block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} state, err = blockExec.ApplyBlock(ctx, state, blockID, block) require.NoError(t, err) // TODO check state and mempool assert.EqualValues(t, 1, state.Version.Consensus.App, "App version wasn't updated") } // TestFinalizeBlockDecidedLastCommit ensures we correctly send the DecidedLastCommit to the // application. The test ensures that the DecidedLastCommit properly reflects // which validators signed the preceding block. func TestFinalizeBlockDecidedLastCommit(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() app := &testApp{} cc := abciclient.NewLocalClient(logger, app) appClient := proxy.New(cc, logger, proxy.NopMetrics()) err := appClient.Start(ctx) require.NoError(t, err) state, stateDB, privVals := makeState(t, 7, 1) stateStore := sm.NewStore(stateDB) absentSig := types.NewCommitSigAbsent() testCases := []struct { name string absentCommitSigs map[int]bool }{ {"none absent", map[int]bool{}}, {"one absent", map[int]bool{1: true}}, {"multiple absent", map[int]bool{1: true, 3: true}}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { blockStore := store.NewBlockStore(dbm.NewMemDB()) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, 0) evpool.On("Update", ctx, mock.Anything, mock.Anything).Return() evpool.On("CheckEvidence", ctx, mock.Anything).Return(nil) mp := &mpmocks.Mempool{} mp.On("Lock").Return() mp.On("Unlock").Return() mp.On("FlushAppConn", mock.Anything).Return(nil) mp.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), appClient, mp, evpool, blockStore, eventBus) state, _, lastCommit := makeAndCommitGoodBlock(ctx, t, state, 1, new(types.Commit), state.NextValidators.Validators[0].Address, blockExec, privVals, nil) for idx, isAbsent := range tc.absentCommitSigs { if isAbsent { lastCommit.Signatures[idx] = absentSig } } // block for height 2 block := sf.MakeBlock(state, 2, lastCommit) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} _, err = blockExec.ApplyBlock(ctx, state, blockID, block) require.NoError(t, err) // -> app receives a list of validators with a bool indicating if they signed for i, v := range app.CommitVotes { _, absent := tc.absentCommitSigs[i] assert.Equal(t, !absent, v.SignedLastBlock) } }) } } // TestFinalizeBlockByzantineValidators ensures we send byzantine validators list. func TestFinalizeBlockByzantineValidators(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() app := &testApp{} logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) state, stateDB, privVals := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC) privVal := privVals[state.Validators.Validators[0].Address.String()] blockID := makeBlockID([]byte("headerhash"), 1000, []byte("partshash")) header := &types.Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 1}, ChainID: state.ChainID, Height: 10, Time: defaultEvidenceTime, LastBlockID: blockID, LastCommitHash: crypto.CRandBytes(tmhash.Size), DataHash: crypto.CRandBytes(tmhash.Size), ValidatorsHash: state.Validators.Hash(), NextValidatorsHash: state.Validators.Hash(), ConsensusHash: crypto.CRandBytes(tmhash.Size), AppHash: crypto.CRandBytes(tmhash.Size), LastResultsHash: crypto.CRandBytes(tmhash.Size), EvidenceHash: crypto.CRandBytes(tmhash.Size), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } // we don't need to worry about validating the evidence as long as they pass validate basic dve, err := types.NewMockDuplicateVoteEvidenceWithValidator(ctx, 3, defaultEvidenceTime, privVal, state.ChainID) require.NoError(t, err) dve.ValidatorPower = 1000 lcae := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ SignedHeader: &types.SignedHeader{ Header: header, Commit: types.NewCommit(10, 0, makeBlockID(header.Hash(), 100, []byte("partshash")), []types.CommitSig{{ BlockIDFlag: types.BlockIDFlagNil, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), Timestamp: defaultEvidenceTime, Signature: crypto.CRandBytes(types.MaxSignatureSize), }}), }, ValidatorSet: state.Validators, }, CommonHeight: 8, ByzantineValidators: []*types.Validator{state.Validators.Validators[0]}, TotalVotingPower: 12, Timestamp: defaultEvidenceTime, } ev := []types.Evidence{dve, lcae} abciEv := []abci.Evidence{ { Type: abci.EvidenceType_DUPLICATE_VOTE, Height: 3, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), TotalVotingPower: 10, }, { Type: abci.EvidenceType_LIGHT_CLIENT_ATTACK, Height: 8, Time: defaultEvidenceTime, Validator: types.TM2PB.Validator(state.Validators.Validators[0]), TotalVotingPower: 12, }, } evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return(ev, int64(100)) evpool.On("Update", ctx, mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return() evpool.On("CheckEvidence", ctx, mock.AnythingOfType("types.EvidenceList")).Return(nil) mp := &mpmocks.Mempool{} mp.On("Lock").Return() mp.On("Unlock").Return() mp.On("FlushAppConn", mock.Anything).Return(nil) mp.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) blockStore := store.NewBlockStore(dbm.NewMemDB()) blockExec := sm.NewBlockExecutor(stateStore, log.NewNopLogger(), proxyApp, mp, evpool, blockStore, eventBus) block := sf.MakeBlock(state, 1, new(types.Commit)) block.Evidence = ev block.Header.EvidenceHash = block.Evidence.Hash() bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} _, err = blockExec.ApplyBlock(ctx, state, blockID, block) require.NoError(t, err) // TODO check state and mempool assert.Equal(t, abciEv, app.ByzantineValidators) } func TestProcessProposal(t *testing.T) { const height = 2 txs := factory.MakeTenTxs(height) ctx, cancel := context.WithCancel(context.Background()) defer cancel() app := abcimocks.NewBaseMock() logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, new(mpmocks.Mempool), sm.EmptyEvidencePool{}, blockStore, eventBus, ) block0 := sf.MakeBlock(state, height-1, new(types.Commit)) lastCommitSig := []types.CommitSig{} partSet, err := block0.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) blockID := types.BlockID{Hash: block0.Hash(), PartSetHeader: partSet.Header()} voteInfos := []abci.VoteInfo{} for _, privVal := range privVals { vote, err := factory.MakeVote(ctx, privVal, block0.Header.ChainID, 0, 0, 0, 2, blockID, time.Now()) require.NoError(t, err) pk, err := privVal.GetPubKey(ctx) require.NoError(t, err) addr := pk.Address() voteInfos = append(voteInfos, abci.VoteInfo{ SignedLastBlock: true, Validator: abci.Validator{ Address: addr, Power: 1000, }, }) lastCommitSig = append(lastCommitSig, vote.CommitSig()) } lastCommit := types.NewCommit(height-1, 0, types.BlockID{}, lastCommitSig) block1 := sf.MakeBlock(state, height, lastCommit) block1.Txs = txs expectedRpp := abci.RequestProcessProposal{ Hash: block1.Hash(), Header: *block1.Header.ToProto(), Txs: block1.Txs.ToSliceOfBytes(), ByzantineValidators: block1.Evidence.ToABCI(), ProposedLastCommit: abci.CommitInfo{ Round: 0, Votes: voteInfos, }, } app.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Accept: true}) acceptBlock, err := blockExec.ProcessProposal(ctx, block1, state) require.NoError(t, err) require.True(t, acceptBlock) app.AssertExpectations(t) app.AssertCalled(t, "ProcessProposal", expectedRpp) } func TestValidateValidatorUpdates(t *testing.T) { pubkey1 := ed25519.GenPrivKey().PubKey() pubkey2 := ed25519.GenPrivKey().PubKey() pk1, err := encoding.PubKeyToProto(pubkey1) assert.NoError(t, err) pk2, err := encoding.PubKeyToProto(pubkey2) assert.NoError(t, err) defaultValidatorParams := types.ValidatorParams{PubKeyTypes: []string{types.ABCIPubKeyTypeEd25519}} testCases := []struct { name string abciUpdates []abci.ValidatorUpdate validatorParams types.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 := encoding.PubKeyToProto(pubkey1) require.NoError(t, err) pk2, err := encoding.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) } } }) } } // TestFinalizeBlockValidatorUpdates ensures we update validator set and send an event. func TestFinalizeBlockValidatorUpdates(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() app := &testApp{} logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) mp := &mpmocks.Mempool{} mp.On("Lock").Return() mp.On("Unlock").Return() mp.On("FlushAppConn", mock.Anything).Return(nil) mp.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, sm.EmptyEvidencePool{}, blockStore, eventBus, ) updatesSub, err := eventBus.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{ ClientID: "TestFinalizeBlockValidatorUpdates", Query: types.EventQueryValidatorSetUpdates, }) require.NoError(t, err) block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} pubkey := ed25519.GenPrivKey().PubKey() pk, err := encoding.PubKeyToProto(pubkey) require.NoError(t, err) app.ValidatorUpdates = []abci.ValidatorUpdate{ {PubKey: pk, Power: 10}, } state, err = blockExec.ApplyBlock(ctx, state, blockID, block) require.NoError(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 ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() msg, err := updatesSub.Next(ctx) require.NoError(t, err) 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) } } // TestFinalizeBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that // would result in empty set causes no panic, an error is raised and NextValidators is not updated func TestFinalizeBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() app := &testApp{} logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, _ := makeState(t, 1, 1) stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) blockExec := sm.NewBlockExecutor( stateStore, log.NewNopLogger(), proxyApp, new(mpmocks.Mempool), sm.EmptyEvidencePool{}, blockStore, eventBus, ) block := sf.MakeBlock(state, 1, new(types.Commit)) bps, err := block.MakePartSet(testPartSize) require.NoError(t, err) blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: bps.Header()} vp, err := encoding.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(ctx, state, blockID, block) }) assert.Error(t, err) assert.NotEmpty(t, state.NextValidators.Validators) } func TestEmptyPrepareProposal(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) app := abcimocks.NewBaseMock() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) mp := &mpmocks.Mempool{} mp.On("Lock").Return() mp.On("Unlock").Return() mp.On("FlushAppConn", mock.Anything).Return(nil) mp.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, sm.EmptyEvidencePool{}, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) _, err = blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) require.NoError(t, err) } // TestPrepareProposalPanicOnInvalid tests that the block creation logic panics // if the ResponsePrepareProposal returned from the application is invalid. func TestPrepareProposalPanicOnInvalid(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) app := abcimocks.NewBaseMock() // create an invalid ResponsePrepareProposal rpp := abci.ResponsePrepareProposal{ ModifiedTx: true, TxRecords: []*abci.TxRecord{ { Action: abci.TxRecord_REMOVED, Tx: []byte("new tx"), }, }, } app.On("PrepareProposal", mock.Anything).Return(rpp, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, evpool, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) require.Panics(t, func() { blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) //nolint:errcheck }) mp.AssertExpectations(t) } // TestPrepareProposalRemoveTxs tests that any transactions marked as REMOVED // are not included in the block produced by CreateProposalBlock. The test also // ensures that any transactions removed are also removed from the mempool. func TestPrepareProposalRemoveTxs(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) txs := factory.MakeTenTxs(height) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) trs := txsToTxRecords(types.Txs(txs)) trs[0].Action = abci.TxRecord_REMOVED trs[1].Action = abci.TxRecord_REMOVED mp.On("RemoveTxByKey", mock.Anything).Return(nil).Twice() app := abcimocks.NewBaseMock() app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ ModifiedTx: true, TxRecords: trs, }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, evpool, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) require.NoError(t, err) require.Len(t, block.Data.Txs.ToSliceOfBytes(), len(trs)-2) require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[0].Tx))) require.Equal(t, -1, block.Data.Txs.Index(types.Tx(trs[1].Tx))) mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[0].Tx).Key()) mp.AssertCalled(t, "RemoveTxByKey", types.Tx(trs[1].Tx).Key()) mp.AssertExpectations(t) } // TestPrepareProposalAddedTxsIncluded tests that any transactions marked as ADDED // in the prepare proposal response are included in the block. The test also // ensures that any transactions added are also checked into the mempool. func TestPrepareProposalAddedTxsIncluded(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) txs := factory.MakeTenTxs(height) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs[2:])) mp.On("CheckTx", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice() trs := txsToTxRecords(types.Txs(txs)) trs[0].Action = abci.TxRecord_ADDED trs[1].Action = abci.TxRecord_ADDED app := abcimocks.NewBaseMock() app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ ModifiedTx: true, TxRecords: trs, }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, evpool, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) require.NoError(t, err) require.Equal(t, txs[0], block.Data.Txs[0]) require.Equal(t, txs[1], block.Data.Txs[1]) mp.AssertExpectations(t) mp.AssertCalled(t, "CheckTx", mock.Anything, types.Tx(trs[0].Tx), mock.Anything, mock.Anything) mp.AssertCalled(t, "CheckTx", mock.Anything, types.Tx(trs[1].Tx), mock.Anything, mock.Anything) } // TestPrepareProposalReorderTxs tests that CreateBlock produces a block with transactions // in the order matching the order they are returned from PrepareProposal. func TestPrepareProposalReorderTxs(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) txs := factory.MakeTenTxs(height) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) trs := txsToTxRecords(types.Txs(txs)) trs = trs[2:] trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...) app := abcimocks.NewBaseMock() app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ ModifiedTx: true, TxRecords: trs, }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, evpool, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) require.NoError(t, err) for i, tx := range block.Data.Txs { require.Equal(t, types.Tx(trs[i].Tx), tx) } mp.AssertExpectations(t) } // TestPrepareProposalModifiedTxFalse tests that CreateBlock correctly ignores // the ResponsePrepareProposal TxRecords if ResponsePrepareProposal does not // set ModifiedTx to true. func TestPrepareProposalModifiedTxFalse(t *testing.T) { const height = 2 ctx, cancel := context.WithCancel(context.Background()) defer cancel() logger := log.NewNopLogger() eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) state, stateDB, privVals := makeState(t, 1, height) stateStore := sm.NewStore(stateDB) evpool := &mocks.EvidencePool{} evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) txs := factory.MakeTenTxs(height) mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) trs := txsToTxRecords(types.Txs(txs)) trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...) trs = trs[1:] trs[0].Action = abci.TxRecord_REMOVED trs[1] = &abci.TxRecord{ Tx: []byte("new"), Action: abci.TxRecord_ADDED, } app := abcimocks.NewBaseMock() app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ ModifiedTx: false, TxRecords: trs, }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) require.NoError(t, err) blockExec := sm.NewBlockExecutor( stateStore, logger, proxyApp, mp, evpool, nil, eventBus, ) pa, _ := state.Validators.GetByIndex(0) commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) require.NoError(t, err) for i, tx := range block.Data.Txs { require.Equal(t, txs[i], tx) } mp.AssertExpectations(t) } func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( h = make([]byte, tmhash.Size) psH = make([]byte, tmhash.Size) ) copy(h, hash) copy(psH, partSetHash) return types.BlockID{ Hash: h, PartSetHeader: types.PartSetHeader{ Total: partSetSize, Hash: psH, }, } } func txsToTxRecords(txs []types.Tx) []*abci.TxRecord { trs := make([]*abci.TxRecord, len(txs)) for i, tx := range txs { trs[i] = &abci.TxRecord{ Action: abci.TxRecord_UNMODIFIED, Tx: tx, } } return trs }