Remove ModifiedTxStatus from the spec and the code (#8210)

* Outstanding abci-gen changes to 'pb.go' files

* Removed modified_tx_status from spec and protobufs

* Fix sed for OSX

* Regenerated abci protobufs with 'abci-proto-gen'

* Code changes. UTs e2e tests passing

* Recovered UT: TestPrepareProposalModifiedTxStatusFalse

* Adapted UT

* Fixed UT

* Revert "Fix sed for OSX"

This reverts commit e576708c61.

* Update internal/state/execution_test.go

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Update abci/example/kvstore/kvstore.go

Co-authored-by: M. J. Fromberger <fromberger@interchain.io>

* Update internal/state/execution_test.go

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Update spec/abci++/abci++_tmint_expected_behavior_002_draft.md

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Addressed some comments

* Added one test that tests error at the ABCI client + Fixed some mock calls

* Addressed remaining comments

* Update abci/example/kvstore/kvstore.go

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Update abci/example/kvstore/kvstore.go

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Update abci/example/kvstore/kvstore.go

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Update spec/abci++/abci++_tmint_expected_behavior_002_draft.md

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>

* Addressed William's latest comments

* Adressed Michael's comment

* Fixed UT

* Some md fixes

* More md fixes

* gofmt

Co-authored-by: William Banfield <4561443+williambanfield@users.noreply.github.com>
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
This commit is contained in:
Sergio Mena
2022-04-04 12:43:01 +02:00
committed by GitHub
parent 9fe25a1ed1
commit 8df38db82e
31 changed files with 1163 additions and 1179 deletions

View File

@@ -6,6 +6,7 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)

View File

@@ -285,8 +285,7 @@ func (app *Application) PrepareProposal(req types.RequestPrepareProposal) types.
defer app.mu.Unlock()
return types.ResponsePrepareProposal{
ModifiedTxStatus: types.ResponsePrepareProposal_MODIFIED,
TxRecords: app.substPrepareTx(req.Txs),
TxRecords: app.substPrepareTx(req.Txs, req.MaxTxBytes),
}
}
@@ -434,28 +433,32 @@ func (app *Application) execPrepareTx(tx []byte) *types.ExecTxResult {
}
// substPrepareTx substitutes all the transactions prefixed with 'prepare' in the
// proposal for transactions with the prefix strips.
// proposal for transactions with the prefix stripped.
// It marks all of the original transactions as 'REMOVED' so that
// Tendermint will remove them from its mempool.
func (app *Application) substPrepareTx(blockData [][]byte) []*types.TxRecord {
trs := make([]*types.TxRecord, len(blockData))
func (app *Application) substPrepareTx(blockData [][]byte, maxTxBytes int64) []*types.TxRecord {
trs := make([]*types.TxRecord, 0, len(blockData))
var removed []*types.TxRecord
for i, tx := range blockData {
var totalBytes int64
for _, tx := range blockData {
txMod := tx
action := types.TxRecord_UNMODIFIED
if isPrepareTx(tx) {
removed = append(removed, &types.TxRecord{
Tx: tx,
Action: types.TxRecord_REMOVED,
})
trs[i] = &types.TxRecord{
Tx: bytes.TrimPrefix(tx, []byte(PreparePrefix)),
Action: types.TxRecord_ADDED,
}
continue
txMod = bytes.TrimPrefix(tx, []byte(PreparePrefix))
action = types.TxRecord_ADDED
}
trs[i] = &types.TxRecord{
Tx: tx,
Action: types.TxRecord_UNMODIFIED,
totalBytes += int64(len(txMod))
if totalBytes > maxTxBytes {
break
}
trs = append(trs, &types.TxRecord{
Tx: txMod,
Action: action,
})
}
return append(trs, removed...)

View File

@@ -95,7 +95,19 @@ func (BaseApplication) ApplySnapshotChunk(req RequestApplySnapshotChunk) Respons
}
func (BaseApplication) PrepareProposal(req RequestPrepareProposal) ResponsePrepareProposal {
return ResponsePrepareProposal{ModifiedTxStatus: ResponsePrepareProposal_UNMODIFIED}
trs := make([]*TxRecord, 0, len(req.Txs))
var totalBytes int64
for _, tx := range req.Txs {
totalBytes += int64(len(tx))
if totalBytes > req.MaxTxBytes {
break
}
trs = append(trs, &TxRecord{
Action: TxRecord_UNMODIFIED,
Tx: tx,
})
}
return ResponsePrepareProposal{TxRecords: trs}
}
func (BaseApplication) ProcessProposal(req RequestProcessProposal) ResponseProcessProposal {

View File

@@ -4,6 +4,7 @@ package mocks
import (
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)

View File

@@ -53,14 +53,6 @@ func (r ResponseQuery) IsErr() bool {
return r.Code != CodeTypeOK
}
func (r ResponsePrepareProposal) IsTxStatusUnknown() bool {
return r.ModifiedTxStatus == ResponsePrepareProposal_UNKNOWN
}
func (r ResponsePrepareProposal) IsTxStatusModified() bool {
return r.ModifiedTxStatus == ResponsePrepareProposal_MODIFIED
}
func (r ResponseProcessProposal) IsAccepted() bool {
return r.Status == ResponseProcessProposal_ACCEPT
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ package debug
import (
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/log"
)

View File

@@ -317,9 +317,20 @@ func (app *CounterApplication) Commit() abci.ResponseCommit {
func (app *CounterApplication) PrepareProposal(
req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
return abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED,
trs := make([]*abci.TxRecord, 0, len(req.Txs))
var totalBytes int64
for _, tx := range req.Txs {
totalBytes += int64(len(tx))
if totalBytes > req.MaxTxBytes {
break
}
trs = append(trs, &abci.TxRecord{
Action: abci.TxRecord_UNMODIFIED,
Tx: tx,
})
}
return abci.ResponsePrepareProposal{TxRecords: trs}
}
func (app *CounterApplication) ProcessProposal(

View File

@@ -4,6 +4,7 @@ package mocks
import (
mock "github.com/stretchr/testify/mock"
state "github.com/tendermint/tendermint/internal/state"
)

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"

View File

@@ -1415,7 +1415,11 @@ func (cs *State) createProposalBlock(ctx context.Context) (*types.Block, error)
proposerAddr := cs.privValidatorPubKey.Address()
return cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, cs.LastCommit.GetVotes())
ret, err := cs.blockExec.CreateProposalBlock(ctx, cs.Height, cs.state, commit, proposerAddr, cs.LastCommit.GetVotes())
if err != nil {
panic(err)
}
return ret, nil
}
// Enter: `timeoutPropose` after entering Propose.

View File

@@ -4,6 +4,7 @@ package mocks
import (
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/types"
)

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/stretchr/testify/require"
abciclient "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/libs/log"

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/go-kit/kit/metrics"
abciclient "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/abci/types"

View File

@@ -14,6 +14,8 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gotest.tools/assert"
abciclient "github.com/tendermint/tendermint/abci/client"
abcimocks "github.com/tendermint/tendermint/abci/client/mocks"
"github.com/tendermint/tendermint/abci/example/kvstore"
@@ -21,7 +23,6 @@ import (
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmrand "github.com/tendermint/tendermint/libs/rand"
"gotest.tools/assert"
)
//----------------------------------------

View File

@@ -121,22 +121,15 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
// transaction causing an error.
//
// Also, the App can simply skip any transaction that could cause any kind of trouble.
// Either way, we can not recover in a meaningful way, unless we skip proposing
// this block, repair what caused the error and try again. Hence, we panic on
// purpose for now.
panic(err)
}
if rpp.IsTxStatusUnknown() {
panic(fmt.Sprintf("PrepareProposal responded with ModifiedTxStatus %s", rpp.ModifiedTxStatus.String()))
}
if !rpp.IsTxStatusModified() {
return block, nil
// Either way, we cannot recover in a meaningful way, unless we skip proposing
// this block, repair what caused the error and try again. Hence, we return an
// error for now (the production code calling this function is expected to panic).
return nil, err
}
txrSet := types.NewTxRecordSet(rpp.TxRecords)
if err := txrSet.Validate(maxDataBytes, block.Txs); err != nil {
panic(fmt.Errorf("ResponsePrepareProposal validation: %w", err))
return nil, err
}
for _, rtx := range txrSet.RemovedTxs() {

View File

@@ -2,6 +2,7 @@ package state_test
import (
"context"
"errors"
"testing"
"time"
@@ -11,6 +12,7 @@ import (
dbm "github.com/tendermint/tm-db"
abciclient "github.com/tendermint/tendermint/abci/client"
abciclientmocks "github.com/tendermint/tendermint/abci/client/mocks"
abci "github.com/tendermint/tendermint/abci/types"
abcimocks "github.com/tendermint/tendermint/abci/types/mocks"
"github.com/tendermint/tendermint/crypto"
@@ -271,7 +273,7 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) {
func TestProcessProposal(t *testing.T) {
const height = 2
txs := factory.MakeTenTxs(height)
txs := factory.MakeNTxs(height, 10)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -617,9 +619,7 @@ func TestEmptyPrepareProposal(t *testing.T) {
require.NoError(t, eventBus.Start(ctx))
app := abcimocks.NewBaseMock()
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED,
}, nil)
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{})
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
err := proxyApp.Start(ctx)
@@ -656,9 +656,10 @@ func TestEmptyPrepareProposal(t *testing.T) {
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) {
// TestPrepareProposalErrorOnNonExistingRemoved tests that the block creation logic returns
// an error if the ResponsePrepareProposal returned from the application marks
// a transaction as REMOVED that was not present in the original proposal.
func TestPrepareProposalErrorOnNonExistingRemoved(t *testing.T) {
const height = 2
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -680,7 +681,6 @@ func TestPrepareProposalPanicOnInvalid(t *testing.T) {
// create an invalid ResponsePrepareProposal
rpp := abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_MODIFIED,
TxRecords: []*abci.TxRecord{
{
Action: abci.TxRecord_REMOVED,
@@ -688,7 +688,7 @@ func TestPrepareProposalPanicOnInvalid(t *testing.T) {
},
},
}
app.On("PrepareProposal", mock.Anything).Return(rpp, nil)
app.On("PrepareProposal", mock.Anything).Return(rpp)
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
@@ -707,10 +707,9 @@ func TestPrepareProposalPanicOnInvalid(t *testing.T) {
)
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
})
block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil)
require.Nil(t, block)
require.ErrorContains(t, err, "new transaction incorrectly marked as removed")
mp.AssertExpectations(t)
}
@@ -733,7 +732,7 @@ func TestPrepareProposalRemoveTxs(t *testing.T) {
evpool := &mocks.EvidencePool{}
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
txs := factory.MakeTenTxs(height)
txs := factory.MakeNTxs(height, 10)
mp := &mpmocks.Mempool{}
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
@@ -744,9 +743,8 @@ func TestPrepareProposalRemoveTxs(t *testing.T) {
app := abcimocks.NewBaseMock()
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_MODIFIED,
TxRecords: trs,
}, nil)
TxRecords: trs,
})
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
@@ -794,7 +792,7 @@ func TestPrepareProposalAddedTxsIncluded(t *testing.T) {
evpool := &mocks.EvidencePool{}
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
txs := factory.MakeTenTxs(height)
txs := factory.MakeNTxs(height, 10)
mp := &mpmocks.Mempool{}
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs[2:]))
@@ -804,9 +802,8 @@ func TestPrepareProposalAddedTxsIncluded(t *testing.T) {
app := abcimocks.NewBaseMock()
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_MODIFIED,
TxRecords: trs,
}, nil)
TxRecords: trs,
})
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
@@ -851,7 +848,7 @@ func TestPrepareProposalReorderTxs(t *testing.T) {
evpool := &mocks.EvidencePool{}
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
txs := factory.MakeTenTxs(height)
txs := factory.MakeNTxs(height, 10)
mp := &mpmocks.Mempool{}
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
@@ -861,9 +858,8 @@ func TestPrepareProposalReorderTxs(t *testing.T) {
app := abcimocks.NewBaseMock()
app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{
ModifiedTxStatus: abci.ResponsePrepareProposal_MODIFIED,
TxRecords: trs,
}, nil)
TxRecords: trs,
})
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
@@ -892,10 +888,9 @@ func TestPrepareProposalReorderTxs(t *testing.T) {
}
// TestPrepareProposalModifiedTxStatusFalse tests that CreateBlock correctly ignores
// the ResponsePrepareProposal TxRecords if ResponsePrepareProposal does not
// set ModifiedTxStatus to true.
func TestPrepareProposalModifiedTxStatusFalse(t *testing.T) {
// TestPrepareProposalErrorOnTooManyTxs tests that the block creation logic returns
// an error if the ResponsePrepareProposal returned from the application is invalid.
func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) {
const height = 2
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -905,29 +900,26 @@ func TestPrepareProposalModifiedTxStatusFalse(t *testing.T) {
require.NoError(t, eventBus.Start(ctx))
state, stateDB, privVals := makeState(t, 1, height)
// limit max block size
state.ConsensusParams.Block.MaxBytes = 60 * 1024
stateStore := sm.NewStore(stateDB)
evpool := &mocks.EvidencePool{}
evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0))
txs := factory.MakeTenTxs(height)
const nValidators = 1
var bytesPerTx int64 = 3
maxDataBytes := types.MaxDataBytes(state.ConsensusParams.Block.MaxBytes, 0, nValidators)
txs := factory.MakeNTxs(height, maxDataBytes/bytesPerTx+2) // +2 so that tx don't fit
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{
ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED,
TxRecords: trs,
}, nil)
TxRecords: trs,
})
cc := abciclient.NewLocalClient(logger, app)
proxyApp := proxy.New(cc, logger, proxy.NopMetrics())
@@ -946,11 +938,62 @@ func TestPrepareProposalModifiedTxStatusFalse(t *testing.T) {
)
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.Nil(t, block)
require.ErrorContains(t, err, "transaction data size exceeds maximum")
mp.AssertExpectations(t)
}
// TestPrepareProposalErrorOnPrepareProposalError tests when the client returns an error
// upon calling PrepareProposal on it.
func TestPrepareProposalErrorOnPrepareProposalError(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.MakeNTxs(height, 10)
mp := &mpmocks.Mempool{}
mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs))
cm := &abciclientmocks.Client{}
cm.On("IsRunning").Return(true)
cm.On("Error").Return(nil)
cm.On("Start", mock.Anything).Return(nil).Once()
cm.On("Wait").Return(nil).Once()
cm.On("PrepareProposal", mock.Anything, mock.Anything).Return(nil, errors.New("an injected error")).Once()
proxyApp := proxy.New(cm, logger, proxy.NopMetrics())
err := proxyApp.Start(ctx)
require.NoError(t, err)
for i, tx := range block.Data.Txs {
require.Equal(t, txs[i], tx)
}
blockExec := sm.NewBlockExecutor(
stateStore,
logger,
proxyApp,
mp,
evpool,
nil,
eventBus,
sm.NopMetrics(),
)
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.Nil(t, block)
require.ErrorContains(t, err, "an injected error")
mp.AssertExpectations(t)
}

View File

@@ -62,7 +62,7 @@ func makeAndApplyGoodBlock(
evidence []types.Evidence,
) (sm.State, types.BlockID) {
t.Helper()
block := state.MakeBlock(height, factory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
block := state.MakeBlock(height, factory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr)
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
require.NoError(t, err)

View File

@@ -6,6 +6,7 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
indexer "github.com/tendermint/tendermint/internal/state/indexer"
query "github.com/tendermint/tendermint/internal/pubsub/query"

View File

@@ -6,6 +6,7 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
state "github.com/tendermint/tendermint/internal/state"
types "github.com/tendermint/tendermint/types"

View File

@@ -4,6 +4,7 @@ package mocks
import (
mock "github.com/stretchr/testify/mock"
state "github.com/tendermint/tendermint/internal/state"
tendermintstate "github.com/tendermint/tendermint/proto/tendermint/state"

View File

@@ -45,7 +45,7 @@ func MakeBlocks(ctx context.Context, t *testing.T, n int, state *sm.State, privV
func MakeBlock(state sm.State, height int64, c *types.Commit) *types.Block {
return state.MakeBlock(
height,
factory.MakeTenTxs(state.LastBlockHeight),
factory.MakeNTxs(state.LastBlockHeight, 10),
c,
nil,
state.Validators.GetProposer().Address,

View File

@@ -338,7 +338,7 @@ func TestValidateBlockEvidence(t *testing.T) {
evidence = append(evidence, newEv)
currentBytes += int64(len(newEv.Bytes()))
}
block := state.MakeBlock(height, testfactory.MakeTenTxs(height), lastCommit, evidence, proposerAddr)
block := state.MakeBlock(height, testfactory.MakeNTxs(height, 10), lastCommit, evidence, proposerAddr)
err := blockExec.ValidateBlock(ctx, state, block)
if assert.Error(t, err) {

View File

@@ -6,6 +6,7 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
state "github.com/tendermint/tendermint/internal/state"
types "github.com/tendermint/tendermint/types"

View File

@@ -2,10 +2,10 @@ package factory
import "github.com/tendermint/tendermint/types"
func MakeTenTxs(height int64) []types.Tx {
txs := make([]types.Tx, 10)
func MakeNTxs(height, n int64) []types.Tx {
txs := make([]types.Tx, n)
for i := range txs {
txs[i] = types.Tx([]byte{byte(height), byte(i)})
txs[i] = types.Tx([]byte{byte(height), byte(i / 256), byte(i % 256)})
}
return txs
}

View File

@@ -316,18 +316,11 @@ message ResponseApplySnapshotChunk {
}
message ResponsePrepareProposal {
ModifiedTxStatus modified_tx_status = 1;
repeated TxRecord tx_records = 2;
bytes app_hash = 3;
repeated ExecTxResult tx_results = 4;
repeated ValidatorUpdate validator_updates = 5;
tendermint.types.ConsensusParams consensus_param_updates = 6;
enum ModifiedTxStatus {
UNKNOWN = 0;
UNMODIFIED = 1;
MODIFIED = 2;
}
repeated TxRecord tx_records = 1;
bytes app_hash = 2;
repeated ExecTxResult tx_results = 3;
repeated ValidatorUpdate validator_updates = 4;
tendermint.types.ConsensusParams consensus_param_updates = 5;
}
message ResponseProcessProposal {

View File

@@ -313,18 +313,11 @@ message ResponseApplySnapshotChunk {
}
message ResponsePrepareProposal {
ModifiedTxStatus modified_tx_status = 1;
repeated TxRecord tx_records = 2;
bytes app_hash = 3;
repeated ExecTxResult tx_results = 4;
repeated ValidatorUpdate validator_updates = 5;
tendermint.types.ConsensusParams consensus_param_updates = 6;
enum ModifiedTxStatus {
UNKNOWN = 0;
UNMODIFIED = 1;
MODIFIED = 2;
}
repeated TxRecord tx_records = 1;
bytes app_hash = 2;
repeated ExecTxResult tx_results = 3;
repeated ValidatorUpdate validator_updates = 4;
tendermint.types.ConsensusParams consensus_param_updates = 5;
}
message ResponseProcessProposal {

View File

@@ -298,7 +298,6 @@ title: Methods
| Name | Type | Description | Field Number |
|-------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------|--------------|
| modified_tx_status | [TxModifiedStatus](#TxModifiedStatus) | `enum` signaling if the application has made changes to the list of transactions. | 1 |
| tx_records | repeated [TxRecord](#txrecord) | Possibly modified list of transactions that have been picked as part of the proposed block. | 2 |
| app_hash | bytes | The Merkle root hash of the application state. | 3 |
| tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 4 |
@@ -311,19 +310,15 @@ title: Methods
* The header contains the height, timestamp, and more - it exactly matches the
Tendermint block header.
* `RequestPrepareProposal` contains a preliminary set of transactions `txs` that Tendermint considers to be a good block proposal, called _raw proposal_. The Application can modify this set via `ResponsePrepareProposal.tx_records` (see [TxRecord](#txrecord)).
* In this case, the Application should set `ResponsePrepareProposal.modified_tx_status` to `MODIFIED`.
* The Application _can_ reorder, remove or add transactions to the raw proposal. Let `tx` be a transaction in `txs`:
* If the Application considers that `tx` should not be proposed in this block, e.g., there are other transactions with higher priority, then it should not include it in `tx_records`. In this case, Tendermint won't remove `tx` from the mempool. The Application should be extra-careful, as abusing this feature may cause transactions to stay forever in the mempool.
* If the Application considers that a `tx` should not be included in the proposal and removed from the mempool, then the Application should include it in `tx_records` and _mark_ it as `REMOVED`. In this case, Tendermint will remove `tx` from the mempool.
* If the Application wants to add a new transaction, then the Application should include it in `tx_records` and _mark_ it as `ADD`. In this case, Tendermint will add it to the mempool.
* The Application should be aware that removing and adding transactions may compromise _traceability_.
> Consider the following example: the Application transforms a client-submitted transaction `t1` into a second transaction `t2`, i.e., the Application asks Tendermint to remove `t1` and add `t2` to the mempool. If a client wants to eventually check what happened to `t1`, it will discover that `t_1` is not in the mempool or in a committed block, getting the wrong idea that `t_1` did not make it into a block. Note that `t_2` _will be_ in a committed block, but unless the Application tracks this information, no component will be aware of it. Thus, if the Application wants traceability, it is its responsability to support it. For instance, the Application could attach to a transformed transaction a list with the hashes of the transactions it derives from.
* If the Application does not modify the preliminary set of transactions `txs`, then it sets `ResponsePrepareProposal.modified_tx_status` to `UNMODIFIED`. In this case, Tendermint will ignore the contents of `ResponsePrepareProposal.tx_records`.
* Tendermint MAY include a list of transactions in `RequestPrepareProposal.txs` whose total size in bytes exceeds `RequestPrepareProposal.max_tx_bytes`.
Therefore, if the size of `RequestPrepareProposal.txs` is greater than `RequestPrepareProposal.max_tx_bytes`:
* the Application MUST make sure that the `RequestPrepareProposal.max_tx_bytes` limit is respected by those
transaction records returned in `ResponsePrepareProposal.tx_records` that are marked as `UNMODIFIED` or `ADDED`.
* the Application MUST set `ResponsePrepareProposal.modified_tx_status` to `MODIFIED`. Tendermint will panic otherwise.
Therefore, if the size of `RequestPrepareProposal.txs` is greater than `RequestPrepareProposal.max_tx_bytes`, the Application MUST make sure that the
`RequestPrepareProposal.max_tx_bytes` limit is respected by those transaction records returned in `ResponsePrepareProposal.tx_records` that are marked as `UNMODIFIED` or `ADDED`.
* In same-block execution mode, the Application must provide values for `ResponsePrepareProposal.app_hash`,
`ResponsePrepareProposal.tx_results`, `ResponsePrepareProposal.validator_updates`, and
`ResponsePrepareProposal.consensus_param_updates`, as a result of fully executing the block.
@@ -413,7 +408,7 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos
| Name | Type | Description | Field Number |
|-------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------|--------------|
| status | [ProposalStatus](#ProposalStatus) | `enum` that signals if the application finds the proposal valid. | 1 |
| status | [ProposalStatus](#proposalstatus) | `enum` that signals if the application finds the proposal valid. | 1 |
| app_hash | bytes | The Merkle root hash of the application state. | 2 |
| tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions. | 3 |
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 4 |
@@ -539,7 +534,7 @@ a [CanonicalVoteExtension](#canonicalvoteextension) field in the `precommit nil`
| Name | Type | Description | Field Number |
|--------|-------------------------------|----------------------------------------------------------------|--------------|
| status | [VerifyStatus](#VerifyStatus) | `enum` signaling if the application accepts the vote extension | 1 |
| status | [VerifyStatus](#verifystatus) | `enum` signaling if the application accepts the vote extension | 1 |
* **Usage**:
* `RequestVerifyVoteExtension.vote_extension` can be an empty byte array. The Application's interpretation of it should be
@@ -832,7 +827,7 @@ Most of the data structures used in ABCI are shared [common data structures](../
| info | string | Additional information. **May be non-deterministic.** | 4 |
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
| events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 |
| events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 |
| codespace | string | Namespace for the `code`. | 8 |
### TxAction
@@ -852,6 +847,7 @@ enum TxAction {
* If `Action` is `ADDED`, Tendermint includes the transaction in the proposal. The transaction is _not_ added to the mempool.
* If `Action` is `REMOVED`, Tendermint excludes the transaction from the proposal. The transaction is also removed from the mempool if it exists,
similar to `CheckTx` returning _false_.
### TxRecord
* **Fields**:
@@ -872,26 +868,10 @@ enum ProposalStatus {
```
* **Usage**:
* Used within the [ProcessProposal](#ProcessProposal) response.
* If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash.
* If `Status` is `ACCEPT`, Tendermint accepts the proposal and will issue a Prevote message for it.
* If `Status` is `REJECT`, Tendermint rejects the proposal and will issue a Prevote for `nil` instead.
### TxModifiedStatus
```proto
enum ModifiedTxStatus {
UNKNOWN = 0; // Unknown status. Returning this from the application is always an error.
UNMODIFIED = 1; // Status that signals the application has modified the returned list of transactions.
MODIFIED = 2; // Status that signals that the application has not modified the list of transactions.
}
```
* **Usage**:
* Used within the [PrepareProposal](#PrepareProposal) response.
* If `TxModifiedStatus` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash.
* If `TxModifiedStatus` is `UNMODIFIED`, Tendermint will ignore the contents of the `PrepareProposal` response and use the transactions originally passed to the application during `PrepareProposal`.
* If `TxModifiedStatus` is `MODIFIED`, Tendermint will update the block proposal using the contents of the `PrepareProposal` response returned by the application.
* Used within the [ProcessProposal](#processproposal) response.
* If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash.
* If `Status` is `ACCEPT`, Tendermint accepts the proposal and will issue a Prevote message for it.
* If `Status` is `REJECT`, Tendermint rejects the proposal and will issue a Prevote for `nil` instead.
### VerifyStatus
@@ -904,10 +884,10 @@ enum VerifyStatus {
```
* **Usage**:
* Used within the [VerifyVoteExtension](#VerifyVoteExtension) response.
* If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash.
* If `Status` is `ACCEPT`, Tendermint will accept the vote as valid.
* If `Status` is `REJECT`, Tendermint will reject the vote as invalid.
* Used within the [VerifyVoteExtension](#verifyvoteextension) response.
* If `Status` is `UNKNOWN`, a problem happened in the Application. Tendermint will assume the application is faulty and crash.
* If `Status` is `ACCEPT`, Tendermint will accept the vote as valid.
* If `Status` is `REJECT`, Tendermint will reject the vote as invalid.
### CanonicalVoteExtension

View File

@@ -202,14 +202,17 @@ to undergo any changes in their implementation.
As for the new methods:
* `PrepareProposal` should check whether the size of transactions exceeds the byte limit.
* If it does: remove transactions at the end of the list until the total byte size conforms to the limit,
then set `ResponsePrepareProposal.modified_tx_status` to `MODIFIED` and return.
* Else, set `ResponsePrepareProposal.modified_tx_status` to `UNMODIFIED` and return.
* `ProcessProposal` should set `ResponseProcessProposal.accept` to _true_ and return.
* `ExtendVote` should set `ResponseExtendVote.extension` to an empty byte array and return.
* `VerifyVoteExtension` should set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array
* `PrepareProposal` must create a list of [TxRecord](./abci++_methods_002_draft.md#txrecord) each containing a
transaction passed in `RequestPrepareProposal.txs`, in the same other. The field `action` must be set to `UNMODIFIED`
for all [TxRecord](./abci++_methods_002_draft.md#txrecord) elements in the list.
The Application must check whether the size of all transactions exceeds the byte limit
(`RequestPrepareProposal.max_tx_bytes`). If so, the Application must remove transactions at the end of the list
until the total byte size is at or below the limit.
* `ProcessProposal` must set `ResponseProcessProposal.accept` to _true_ and return.
* `ExtendVote` is to set `ResponseExtendVote.extension` to an empty byte array and return.
* `VerifyVoteExtension` must set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array
and _false_ otherwise, then return.
* `FinalizeBlock` should coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`.
The logic extracted from `DeliverTx` should be wrappped by a loop that will execute as many times as
transactions exist in `RequestFinalizeBlock.tx`.
* `FinalizeBlock` is to coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`.
Legacy applications looking to reuse old code that implemented `DeliverTx` should wrap the legacy
`DeliverTx` logic in a loop that executes one transaction iteration per
transaction in `RequestFinalizeBlock.tx`.

View File

@@ -306,7 +306,19 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a
func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
// None of the transactions are modified by this application.
return abci.ResponsePrepareProposal{ModifiedTxStatus: abci.ResponsePrepareProposal_UNMODIFIED}
trs := make([]*abci.TxRecord, 0, len(req.Txs))
var totalBytes int64
for _, tx := range req.Txs {
totalBytes += int64(len(tx))
if totalBytes > req.MaxTxBytes {
break
}
trs = append(trs, &abci.TxRecord{
Action: abci.TxRecord_UNMODIFIED,
Tx: tx,
})
}
return abci.ResponsePrepareProposal{TxRecords: trs}
}
// ProcessProposal implements part of the Application interface.

View File

@@ -205,7 +205,7 @@ func (t TxRecordSet) Validate(maxSizeBytes int64, otxs Txs) error {
for _, cur := range append(unmodifiedCopy, addedCopy...) {
size += int64(len(cur))
if size > maxSizeBytes {
return fmt.Errorf("transaction data size %d exceeds maximum %d", size, maxSizeBytes)
return fmt.Errorf("transaction data size exceeds maximum %d", maxSizeBytes)
}
}