Skip to content

Commit

Permalink
feat: ABCI 1.0 baseapp integration (#13453)
Browse files Browse the repository at this point in the history
  • Loading branch information
kocubinski authored Nov 9, 2022
1 parent 90fd3e9 commit 61effe8
Show file tree
Hide file tree
Showing 21 changed files with 654 additions and 144 deletions.
71 changes: 60 additions & 11 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/mempool"
)

// Supported ABCI Query prefixes
Expand All @@ -38,6 +39,8 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
// req.InitialHeight is 1 by default.
initHeader := tmproto.Header{ChainID: req.ChainId, Time: req.Time}

app.logger.Info("InitChain", "initialHeight", req.InitialHeight, "chainID", req.ChainId)

// If req.InitialHeight is > 1, then we set the initial version in the
// stores.
if req.InitialHeight > 1 {
Expand All @@ -49,9 +52,11 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
}
}

// initialize the deliver state and check state with a correct header
// initialize states with a correct header
app.setDeliverState(initHeader)
app.setCheckState(initHeader)
app.setPrepareProposalState(initHeader)
app.setProcessProposalState(initHeader)

// Store the consensus params in the BaseApp's paramstore. Note, this must be
// done after the deliver state and context have been set as it's persisted
Expand Down Expand Up @@ -182,8 +187,6 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
WithHeaderHash(req.Hash).
WithConsensusParams(app.GetConsensusParams(app.deliverState.ctx))

// we also set block gas meter to checkState in case the application needs to
// verify gas consumption during (Re)CheckTx
if app.checkState != nil {
app.checkState.ctx = app.checkState.ctx.
WithBlockGasMeter(gasMeter).
Expand Down Expand Up @@ -238,19 +241,52 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
// work in a block before proposing it.
//
// Transactions can be modified, removed, or added by the application. Since the
// application maintains it's own local mempool, it will ignore the transactions
// application maintains its own local mempool, it will ignore the transactions
// provided to it in RequestPrepareProposal. Instead, it will determine which
// transactions to return based on the mempool's semantics and the MaxTxBytes
// provided by the client's request.
//
// Note, there is no need to execute the transactions for validity as they have
// already passed CheckTx.
//
// Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
// Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
// TODO: Implement.
return abci.ResponsePrepareProposal{Txs: req.Txs}
var (
txsBytes [][]byte
byteCount int64
)

ctx := app.getContextForTx(runTxPrepareProposal, []byte{})
iterator := app.mempool.Select(ctx, req.Txs)

for iterator != nil {
memTx := iterator.Tx()

bz, err := app.txEncoder(memTx)
if err != nil {
panic(err)
}

txSize := int64(len(bz))

// NOTE: runTx was already run in CheckTx which calls mempool.Insert so ideally everything in the pool
// should be valid. But some mempool implementations may insert invalid txs, so we check again.
_, _, _, _, err = app.runTx(runTxPrepareProposal, bz)
if err != nil {
err := app.mempool.Remove(memTx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
panic(err)
}
iterator = iterator.Next()
continue
} else if byteCount += txSize; byteCount <= req.MaxTxBytes {
txsBytes = append(txsBytes, bz)
} else {
break
}

iterator = iterator.Next()
}

return abci.ResponsePrepareProposal{Txs: txsBytes}
}

// ProcessProposal implements the ProcessProposal ABCI method and returns a
Expand All @@ -266,8 +302,19 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon
// Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
// Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
// TODO: Implement.
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
if app.processProposal == nil {
panic("app.ProcessProposal is not set")
}

ctx := app.processProposalState.ctx.
WithVoteInfos(app.voteInfos).
WithBlockHeight(req.Height).
WithBlockTime(req.Time).
WithHeaderHash(req.Hash).
WithProposer(req.ProposerAddress).
WithConsensusParams(app.GetConsensusParams(app.processProposalState.ctx))

return app.processProposal(ctx, req)
}

// CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In
Expand Down Expand Up @@ -367,6 +414,8 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
// NOTE: This is safe because Tendermint holds a lock on the mempool for
// Commit. Use the header from this latest block.
app.setCheckState(header)
app.setPrepareProposalState(header)
app.setProcessProposalState(header)

// empty/reset the deliver state
app.deliverState = nil
Expand Down
196 changes: 196 additions & 0 deletions baseapp/abci_v1_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package baseapp_test

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"cosmossdk.io/depinject"
"github.com/cosmos/cosmos-sdk/baseapp"
baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/mempool"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
)

type NoopCounterServerImpl struct{}

func (m NoopCounterServerImpl) IncrementCounter(
_ context.Context,
_ *baseapptestutil.MsgCounter,
) (*baseapptestutil.MsgCreateCounterResponse, error) {
return &baseapptestutil.MsgCreateCounterResponse{}, nil
}

type ABCIv1TestSuite struct {
suite.Suite
baseApp *baseapp.BaseApp
mempool mempool.Mempool
txConfig client.TxConfig
cdc codec.ProtoCodecMarshaler
}

func TestABCIv1TestSuite(t *testing.T) {
suite.Run(t, new(ABCIv1TestSuite))
}

func (s *ABCIv1TestSuite) SetupTest() {
t := s.T()
anteKey := []byte("ante-key")
pool := mempool.NewNonceMempool()
anteOpt := func(bapp *baseapp.BaseApp) {
bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey))
}

var (
appBuilder *runtime.AppBuilder
cdc codec.ProtoCodecMarshaler
registry codectypes.InterfaceRegistry
)
err := depinject.Inject(makeMinimalConfig(), &appBuilder, &cdc, &registry)
require.NoError(t, err)

app := setupBaseApp(t, anteOpt, baseapp.SetMempool(pool))
baseapptestutil.RegisterInterfaces(registry)
app.SetMsgServiceRouter(baseapp.NewMsgServiceRouter())
app.SetInterfaceRegistry(registry)

baseapptestutil.RegisterKeyValueServer(app.MsgServiceRouter(), MsgKeyValueImpl{})
baseapptestutil.RegisterCounterServer(app.MsgServiceRouter(), NoopCounterServerImpl{})
header := tmproto.Header{Height: app.LastBlockHeight() + 1}

app.InitChain(abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
})

app.BeginBlock(abci.RequestBeginBlock{Header: header})

// patch in TxConfig insted of using an output from x/auth/tx
txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes)

app.SetTxDecoder(txConfig.TxDecoder())
app.SetTxEncoder(txConfig.TxEncoder())

s.baseApp = app
s.mempool = pool
s.txConfig = txConfig
s.cdc = cdc
}

func (s *ABCIv1TestSuite) TestABCIv1_HappyPath() {
txConfig := s.txConfig
t := s.T()

tx := newTxCounter(txConfig, 0, 1)
txBytes, err := txConfig.TxEncoder()(tx)
require.NoError(t, err)

reqCheckTx := abci.RequestCheckTx{
Tx: txBytes,
Type: abci.CheckTxType_New,
}
s.baseApp.CheckTx(reqCheckTx)

tx2 := newTxCounter(txConfig, 1, 1)

tx2Bytes, err := txConfig.TxEncoder()(tx2)
require.NoError(t, err)

err = s.mempool.Insert(sdk.Context{}, tx2)
require.NoError(t, err)
reqPreparePropossal := abci.RequestPrepareProposal{
MaxTxBytes: 1000,
}
resPreparePropossal := s.baseApp.PrepareProposal(reqPreparePropossal)

require.Equal(t, 2, len(resPreparePropossal.Txs))

var reqProposalTxBytes [2][]byte
reqProposalTxBytes[0] = txBytes
reqProposalTxBytes[1] = tx2Bytes
reqProcessProposal := abci.RequestProcessProposal{
Txs: reqProposalTxBytes[:],
}

s.baseApp.SetProcessProposal(nil)
require.Panics(t, func() { s.baseApp.ProcessProposal(reqProcessProposal) })
s.baseApp.SetProcessProposal(s.baseApp.DefaultProcessProposal())

resProcessProposal := s.baseApp.ProcessProposal(reqProcessProposal)
require.Equal(t, abci.ResponseProcessProposal_ACCEPT, resProcessProposal.Status)

res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.Equal(t, 1, s.mempool.CountTx())

require.NotEmpty(t, res.Events)
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
}

func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_ReachedMaxBytes() {
txConfig := s.txConfig
t := s.T()

for i := 0; i < 100; i++ {
tx2 := newTxCounter(txConfig, int64(i), int64(i))
err := s.mempool.Insert(sdk.Context{}, tx2)
require.NoError(t, err)
}

reqPreparePropossal := abci.RequestPrepareProposal{
MaxTxBytes: 1500,
}
resPreparePropossal := s.baseApp.PrepareProposal(reqPreparePropossal)

require.Equal(t, 10, len(resPreparePropossal.Txs))
}

func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_BadEncoding() {
txConfig := authtx.NewTxConfig(s.cdc, authtx.DefaultSignModes)

t := s.T()

tx := newTxCounter(txConfig, 0, 0)
err := s.mempool.Insert(sdk.Context{}, tx)
require.NoError(t, err)

reqPrepareProposal := abci.RequestPrepareProposal{
MaxTxBytes: 1000,
}
resPrepareProposal := s.baseApp.PrepareProposal(reqPrepareProposal)

require.Equal(t, 1, len(resPrepareProposal.Txs))
}

func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_Failures() {
tx := newTxCounter(s.txConfig, 0, 0)
txBytes, err := s.txConfig.TxEncoder()(tx)
s.NoError(err)

reqCheckTx := abci.RequestCheckTx{
Tx: txBytes,
Type: abci.CheckTxType_New,
}
checkTxRes := s.baseApp.CheckTx(reqCheckTx)
s.True(checkTxRes.IsOK())

failTx := newTxCounter(s.txConfig, 1, 1)
failTx = setFailOnAnte(s.txConfig, failTx, true)
err = s.mempool.Insert(sdk.Context{}, failTx)
s.NoError(err)
s.Equal(2, s.mempool.CountTx())

req := abci.RequestPrepareProposal{
MaxTxBytes: 1000,
}
res := s.baseApp.PrepareProposal(req)
s.Equal(1, len(res.Txs))
}
Loading

0 comments on commit 61effe8

Please sign in to comment.