diff --git a/abci/types/types.go b/abci/types/types.go index 193cc42c2..cfdc13a63 100644 --- a/abci/types/types.go +++ b/abci/types/types.go @@ -247,6 +247,13 @@ func (rpp *ResponsePrepareProposal) AddedTxs() []*TxRecord { // Validate checks that the fields of the ResponsePrepareProposal are properly // constructed. Validate returns an error if any of the validation checks fail. func (rpp *ResponsePrepareProposal) Validate(maxSizeBytes int64, otxs [][]byte) error { + + if !rpp.ModifiedTx { + // This method currently only checks the validity of the TxRecords field. + // If ModifiedTx is false, then we can ignore the validity of the TxRecords field. + return nil + } + // TODO: this feels like a large amount allocated data. We move all the Txs into strings // in the map. The map will be as large as the original byte slice. // Is there a key we can use? diff --git a/internal/state/execution.go b/internal/state/execution.go index fa125ccbb..aebaa3abb 100644 --- a/internal/state/execution.go +++ b/internal/state/execution.go @@ -139,9 +139,15 @@ func (blockExec *BlockExecutor) CreateProposalBlock( // purpose for now. panic(err) } + if err := rpp.Validate(maxDataBytes, txs.ToSliceOfBytes()); err != nil { return nil, err } + + if !rpp.ModifiedTx { + return state.MakeBlock(height, txs, commit, evidence, proposerAddr) + } + for _, rtx := range rpp.RemovedTxs() { if err := blockExec.mempool.RemoveTxByKey(types.Tx(rtx.Tx).Key()); err != nil { blockExec.logger.Debug("error removing transaction from the mempool", "error", err) diff --git a/internal/state/execution_test.go b/internal/state/execution_test.go index 39a6b4f3b..96c126098 100644 --- a/internal/state/execution_test.go +++ b/internal/state/execution_test.go @@ -818,6 +818,64 @@ func TestPrepareProposalReorderTxs(t *testing.T) { require.Equal(t, types.Tx(trs[i].Tx), tx) } + mp.AssertExpectations(t) + +} + +// TestPrepareProposalReorderTxs tests that CreateBlock produces a block with transactions +// in the order matching the order they are returned from PrepareProposal. +func TestPrepareProposalModifiedTxFalse(t *testing.T) { + const height = 2 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + 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 := types.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.NewLocalCreator(app) + logger := log.TestingLogger() + proxyApp := proxy.NewAppConns(cc, logger, proxy.NopMetrics()) + err := proxyApp.Start(ctx) + require.NoError(t, err) + + blockExec := sm.NewBlockExecutor( + stateStore, + logger, + proxyApp.Consensus(), + mp, + evpool, + nil, + ) + pa, _ := state.Validators.GetByIndex(0) + commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa) + require.NoError(t, err) + for i, tx := range block.Data.Txs { + require.Equal(t, txs[i], tx) + } + mp.AssertExpectations(t) }