From 61effe82603006a7192cb311787ca8fc776a4461 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 9 Nov 2022 09:50:27 -0600 Subject: [PATCH] feat: ABCI 1.0 baseapp integration (#13453) --- baseapp/abci.go | 71 +++++++++-- baseapp/abci_v1_test.go | 196 +++++++++++++++++++++++++++++++ baseapp/baseapp.go | 127 ++++++++++++++++---- baseapp/deliver_tx_test.go | 30 ++++- baseapp/options.go | 25 ++++ baseapp/testutil/messages.go | 3 + baseapp/util_test.go | 23 ++-- server/mock/app_test.go | 10 +- server/mock/tx.go | 78 ++++++++++-- server/types/app.go | 2 +- simapp/app_legacy.go | 1 + types/abci.go | 3 + types/mempool/mempool.go | 37 +++--- types/mempool/mempool_test.go | 57 ++++++++- types/mempool/nonce.go | 53 +++++---- types/mempool/nonce_test.go | 4 +- x/auth/tx/builder.go | 38 +----- x/auth/tx/module/module.go | 3 +- x/distribution/types/codec.go | 5 + x/distribution/types/proposal.go | 30 +++++ x/genutil/gentx.go | 2 +- 21 files changed, 654 insertions(+), 144 deletions(-) create mode 100644 baseapp/abci_v1_test.go diff --git a/baseapp/abci.go b/baseapp/abci.go index 4a2998a50db2..def13328aaa7 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -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 @@ -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 { @@ -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 @@ -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). @@ -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 @@ -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 @@ -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 diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go new file mode 100644 index 000000000000..83cb7c6a75b0 --- /dev/null +++ b/baseapp/abci_v1_test.go @@ -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, ®istry) + 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)) +} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 2ff90cb8effb..08a8746589c5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,10 +1,12 @@ package baseapp import ( + "errors" "fmt" "sort" "strings" + "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" @@ -20,7 +22,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/mempool" - "github.com/cosmos/gogoproto/proto" ) const ( @@ -28,6 +29,8 @@ const ( runTxModeReCheck // Recheck a (pending) transaction after a commit runTxModeSimulate // Simulate a transaction runTxModeDeliver // Deliver a transaction + runTxPrepareProposal + runTxProcessProposal ) var _ abci.Application = (*BaseApp)(nil) @@ -56,16 +59,18 @@ type BaseApp struct { //nolint: maligned msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - - mempool mempool.Mempool // application side mempool - anteHandler sdk.AnteHandler // ante handler for fee and auth - postHandler sdk.AnteHandler // post handler, optional, e.g. for tips - initChainer sdk.InitChainer // initialize state with validators and state blob - beginBlocker sdk.BeginBlocker // logic to run before any txs - endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes - addrPeerFilter sdk.PeerFilter // filter peers by address and port - idPeerFilter sdk.PeerFilter // filter peers by node ID - fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. + txEncoder sdk.TxEncoder // marshal sdk.Tx into []byte + + mempool mempool.Mempool // application side mempool + anteHandler sdk.AnteHandler // ante handler for fee and auth + postHandler sdk.AnteHandler // post handler, optional, e.g. for tips + initChainer sdk.InitChainer // initialize state with validators and state blob + beginBlocker sdk.BeginBlocker // logic to run before any txs + processProposal sdk.ProcessProposalHandler // the handler which runs on ABCI ProcessProposal + endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes + addrPeerFilter sdk.PeerFilter // filter peers by address and port + idPeerFilter sdk.PeerFilter // filter peers by node ID + fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. // manages snapshots, i.e. dumps of app state at certain intervals snapshotManager *snapshots.Manager @@ -74,8 +79,10 @@ type BaseApp struct { //nolint: maligned // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit - checkState *state // for CheckTx - deliverState *state // for DeliverTx + checkState *state // for CheckTx + deliverState *state // for DeliverTx + processProposalState *state // for ProcessProposal + prepareProposalState *state // for PrepareProposal // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache @@ -161,6 +168,14 @@ func NewBaseApp( option(app) } + if app.mempool == nil { + app.SetMempool(mempool.NewNonceMempool()) + } + + if app.processProposal == nil { + app.SetProcessProposal(app.DefaultProcessProposal()) + } + if app.interBlockCache != nil { app.cms.SetInterBlockCache(app.interBlockCache) } @@ -328,8 +343,14 @@ func (app *BaseApp) Init() error { panic("cannot call initFromMainStore: baseapp already sealed") } + emptyHeader := tmproto.Header{} + // needed for the export command which inits from store but never calls initchain - app.setCheckState(tmproto.Header{}) + app.setCheckState(emptyHeader) + + // needed for ABCI Replay Blocks mode which calls Prepare/Process proposal (InitChain is not called) + app.setPrepareProposalState(emptyHeader) + app.setProcessProposalState(emptyHeader) app.Seal() rms, ok := app.cms.(*rootmulti.Store) @@ -401,6 +422,28 @@ func (app *BaseApp) setDeliverState(header tmproto.Header) { } } +// setPrepareProposalState sets the BaseApp's prepareProposalState with a +// branched multi-store (i.e. a CacheMultiStore) and a new Context with the +// same multi-store branch, and provided header. It is set on InitChain and Commit. +func (app *BaseApp) setPrepareProposalState(header tmproto.Header) { + ms := app.cms.CacheMultiStore() + app.prepareProposalState = &state{ + ms: ms, + ctx: sdk.NewContext(ms, header, false, app.logger), + } +} + +// setProcessProposalState sets the BaseApp's processProposalState with a +// branched multi-store (i.e. a CacheMultiStore) and a new Context with the +// same multi-store branch, and provided header. It is set on InitChain and Commit. +func (app *BaseApp) setProcessProposalState(header tmproto.Header) { + ms := app.cms.CacheMultiStore() + app.processProposalState = &state{ + ms: ms, + ctx: sdk.NewContext(ms, header, false, app.logger), + } +} + // GetConsensusParams returns the current consensus parameters from the BaseApp's // ParamStore. If the BaseApp has no ParamStore defined, nil is returned. func (app *BaseApp) GetConsensusParams(ctx sdk.Context) *tmproto.ConsensusParams { @@ -507,16 +550,25 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error { // Returns the application's deliverState if app is in runTxModeDeliver, // otherwise it returns the application's checkstate. func (app *BaseApp) getState(mode runTxMode) *state { - if mode == runTxModeDeliver { + switch mode { + case runTxModeDeliver: return app.deliverState + case runTxPrepareProposal: + return app.prepareProposalState + case runTxProcessProposal: + return app.processProposalState + default: + return app.checkState } - - return app.checkState } // retrieve the context for the tx w/ txBytes and other memoized values. func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context { - ctx := app.getState(mode).ctx. + modeState := app.getState(mode) + if modeState == nil { + panic(fmt.Sprintf("state is nil for mode %v", mode)) + } + ctx := modeState.ctx. WithTxBytes(txBytes). WithVoteInfos(app.voteInfos) @@ -654,12 +706,17 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re anteEvents = events.ToABCIEvents() } - // TODO remove nil check when implemented - if mode == runTxModeCheck && app.mempool != nil { - err = app.mempool.Insert(ctx, tx.(mempool.Tx)) + if mode == runTxModeCheck { + err = app.mempool.Insert(ctx, tx) if err != nil { return gInfo, nil, anteEvents, priority, err } + } else if mode == runTxModeDeliver { + err = app.mempool.Remove(tx) + if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { + return gInfo, nil, anteEvents, priority, + fmt.Errorf("failed to remove tx from mempool: %w", err) + } } // Create a new Context based off of the existing Context with a MultiStore branch @@ -713,8 +770,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { - // skip actual execution for (Re)CheckTx mode - if mode == runTxModeCheck || mode == runTxModeReCheck { + + if mode != runTxModeDeliver && mode != runTxModeSimulate { break } @@ -790,3 +847,27 @@ func createEvents(msg sdk.Msg) sdk.Events { return sdk.Events{msgEvent} } + +// DefaultProcessProposal returns the default implementation for processing an ABCI proposal. +// Every transaction in the proposal must pass 2 conditions: +// +// 1. The transaction bytes must decode to a valid transaction. +// 2. The transaction must be valid (i.e. pass runTx, AnteHandler only) +// +// If any transaction fails to pass either condition, the proposal is rejected. +func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { + return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal { + for _, txBytes := range req.Txs { + _, err := app.txDecoder(txBytes) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + + _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + } + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + } +} diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index dd819cccdd0e..79be7771981d 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -17,8 +17,9 @@ import ( "time" "unsafe" - "cosmossdk.io/depinject" - "github.com/cosmos/gogoproto/jsonpb" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -26,6 +27,10 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/gogoproto/jsonpb" + + "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" "github.com/cosmos/cosmos-sdk/client" @@ -70,6 +75,7 @@ func defaultLogger() log.Logger { func setupBaseApp(t *testing.T, options ...func(*baseapp.BaseApp)) *baseapp.BaseApp { cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) + txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes) logger := defaultLogger() @@ -129,6 +135,7 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base builder := txConfig.NewTxBuilder() builder.SetMsgs(msgs...) + setTxSignature(builder, 0) txBytes, err := txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) @@ -1061,9 +1068,9 @@ func TestCheckTx(t *testing.T) { require.NoError(t, err) r := app.CheckTx(abci.RequestCheckTx{Tx: txBytes}) + require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) require.Equal(t, testTxPriority, r.Priority) require.Empty(t, r.GetEvents()) - require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) } checkStateStore := getCheckStateCtx(app.BaseApp).KVStore(capKey1) @@ -1208,6 +1215,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { builder.SetMsgs(msgs...) builder.SetMemo(tx.GetMemo()) + setTxSignature(builder, 0) txBytes, err = txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) @@ -1389,6 +1397,7 @@ func TestRunInvalidTransaction(t *testing.T) { { txBuilder := txConfig.NewTxBuilder() txBuilder.SetMsgs(&baseapptestutil.MsgCounter2{}) + setTxSignature(txBuilder, 0) unknownRouteTx := txBuilder.GetTx() _, result, err := app.SimDeliver(txConfig.TxEncoder(), unknownRouteTx) @@ -1401,6 +1410,7 @@ func TestRunInvalidTransaction(t *testing.T) { txBuilder = txConfig.NewTxBuilder() txBuilder.SetMsgs(&baseapptestutil.MsgCounter{}, &baseapptestutil.MsgCounter2{}) + setTxSignature(txBuilder, 0) unknownRouteTx = txBuilder.GetTx() _, result, err = app.SimDeliver(txConfig.TxEncoder(), unknownRouteTx) require.Error(t, err) @@ -1989,6 +1999,7 @@ func newTxCounter(cfg client.TxConfig, counter int64, msgCounters ...int64) sign builder := cfg.NewTxBuilder() builder.SetMsgs(msgs...) builder.SetMemo("counter=" + strconv.FormatInt(counter, 10) + "&failOnAnte=false") + setTxSignature(builder, uint64(counter)) return builder.GetTx() } @@ -2006,6 +2017,7 @@ func setFailOnAnte(cfg client.TxConfig, tx signing.Tx, failOnAnte bool) signing. vals.Set("failOnAnte", strconv.FormatBool(failOnAnte)) memo = vals.Encode() builder.SetMemo(memo) + setTxSignature(builder, 1) return builder.GetTx() } @@ -2201,3 +2213,15 @@ func (ps paramStore) Get(ctx sdk.Context) (*tmproto.ConsensusParams, error) { return ¶ms, nil } + +func setTxSignature(builder client.TxBuilder, nonce uint64) { + privKey := secp256k1.GenPrivKeyFromSecret([]byte("test")) + pubKey := privKey.PubKey() + err := builder.SetSignatures( + signingtypes.SignatureV2{ + PubKey: pubKey, Sequence: nonce, Data: &signingtypes.SingleSignatureData{}, + }) + if err != nil { + panic(err) + } +} diff --git a/baseapp/options.go b/baseapp/options.go index aab297edb52e..d655a0e756c8 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/store" pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" ) // File for storing in-package BaseApp optional functions, @@ -80,6 +81,16 @@ func SetSnapshot(snapshotStore *snapshots.Store, opts snapshottypes.SnapshotOpti return func(app *BaseApp) { app.SetSnapshot(snapshotStore, opts) } } +// SetMempool sets the mempool on BaseApp. +func SetMempool(mempool mempool.Mempool) func(*BaseApp) { + return func(app *BaseApp) { app.SetMempool(mempool) } +} + +// SetProcessProposal sets the ProcessProposal handler. +func SetProcessProposal(proposalHandler sdk.ProcessProposalHandler) func(*BaseApp) { + return func(app *BaseApp) { app.SetProcessProposal(proposalHandler) } +} + func (app *BaseApp) SetName(name string) { if app.sealed { panic("SetName() on sealed BaseApp") @@ -241,9 +252,23 @@ func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) { app.txDecoder = txDecoder } +// SetTxEncoder sets the TxEncoder if it wasn't provided in the BaseApp constructor. +func (app *BaseApp) SetTxEncoder(txEncoder sdk.TxEncoder) { + app.txEncoder = txEncoder +} + // SetQueryMultiStore set a alternative MultiStore implementation to support grpc query service. // // Ref: https://github.com/cosmos/cosmos-sdk/issues/13317 func (app *BaseApp) SetQueryMultiStore(ms sdk.MultiStore) { app.qms = ms } + +// SetMempool sets the mempool for the BaseApp and is required for the app to start up. +func (app *BaseApp) SetMempool(mempool mempool.Mempool) { + app.mempool = mempool +} + +func (app *BaseApp) SetProcessProposal(handler sdk.ProcessProposalHandler) { + app.processProposal = handler +} diff --git a/baseapp/testutil/messages.go b/baseapp/testutil/messages.go index cbe515199216..b98c192a7e00 100644 --- a/baseapp/testutil/messages.go +++ b/baseapp/testutil/messages.go @@ -2,6 +2,7 @@ package testutil import ( "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/msgservice" @@ -17,6 +18,8 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Counter_serviceDesc) msgservice.RegisterMsgServiceDesc(registry, &_Counter2_serviceDesc) msgservice.RegisterMsgServiceDesc(registry, &_KeyValue_serviceDesc) + + codec.RegisterInterfaces(registry) } var _ sdk.Msg = &MsgCounter{} diff --git a/baseapp/util_test.go b/baseapp/util_test.go index 8acd70d52884..91a23b8f3177 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -18,12 +18,14 @@ import ( txmodulev1 "cosmossdk.io/api/cosmos/tx/module/v1" "cosmossdk.io/core/appconfig" "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil/mock" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" _ "github.com/cosmos/cosmos-sdk/x/auth" _ "github.com/cosmos/cosmos-sdk/x/auth/tx/module" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -148,14 +150,17 @@ func makeTestConfig() depinject.Config { } func makeMinimalConfig() depinject.Config { - return appconfig.Compose(&appv1alpha1.Config{ - Modules: []*appv1alpha1.ModuleConfig{ - { - Name: "runtime", - Config: appconfig.WrapAny(&runtimev1alpha1.Module{ - AppName: "BaseAppApp", - }), + var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewNonceMempool()) + return depinject.Configs( + depinject.Supply(mempoolOpt), + appconfig.Compose(&appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + { + Name: "runtime", + Config: appconfig.WrapAny(&runtimev1alpha1.Module{ + AppName: "BaseAppApp", + }), + }, }, - }, - }) + })) } diff --git a/server/mock/app_test.go b/server/mock/app_test.go index 94362c887068..6ae13632d8ea 100644 --- a/server/mock/app_test.go +++ b/server/mock/app_test.go @@ -1,6 +1,10 @@ package mock import ( + "math/rand" + "time" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "testing" "github.com/stretchr/testify/require" @@ -53,7 +57,11 @@ func TestDeliverTx(t *testing.T) { key := "my-special-key" value := "top-secret-data!!" - tx := NewTx(key, value) + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + randomAccounts := simtypes.RandomAccounts(r, 1) + + tx := NewTx(key, value, randomAccounts[0].Address) txBytes := tx.GetSignBytes() header := tmproto.Header{ diff --git a/server/mock/tx.go b/server/mock/tx.go index 40555e8724e8..6b4c499c8380 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -4,15 +4,66 @@ import ( "bytes" "fmt" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // An sdk.Tx which is its own sdk.Msg. type kvstoreTx struct { - key []byte - value []byte - bytes []byte + key []byte + value []byte + bytes []byte + address sdk.AccAddress +} +type testPubKey struct { + address sdk.AccAddress +} + +func (t testPubKey) Reset() { panic("implement me") } + +func (t testPubKey) String() string { panic("implement me") } + +func (t testPubKey) ProtoMessage() { panic("implement me") } + +func (t testPubKey) Address() cryptotypes.Address { return t.address.Bytes() } + +func (t testPubKey) Bytes() []byte { panic("implement me") } + +func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { panic("implement me") } + +func (t testPubKey) Equals(key cryptotypes.PubKey) bool { panic("implement me") } + +func (t testPubKey) Type() string { panic("implement me") } + +func (msg *kvstoreTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { + res = append(res, txsigning.SignatureV2{ + PubKey: testPubKey{address: msg.address}, + Data: nil, + Sequence: 1, + }) + + return res, nil +} + +func (msg *kvstoreTx) VerifySignature(msgByte []byte, sig []byte) bool { + panic("implement me") +} + +func (msg *kvstoreTx) Address() cryptotypes.Address { + panic("implement me") +} + +func (msg *kvstoreTx) Bytes() []byte { + panic("implement me") +} + +func (msg *kvstoreTx) Equals(key cryptotypes.PubKey) bool { + panic("implement me") } // dummy implementation of proto.Message @@ -21,16 +72,19 @@ func (msg *kvstoreTx) String() string { return "TODO" } func (msg *kvstoreTx) ProtoMessage() {} var ( - _ sdk.Tx = &kvstoreTx{} - _ sdk.Msg = &kvstoreTx{} + _ sdk.Tx = &kvstoreTx{} + _ sdk.Msg = &kvstoreTx{} + _ signing.SigVerifiableTx = &kvstoreTx{} + _ cryptotypes.PubKey = &kvstoreTx{} ) -func NewTx(key, value string) *kvstoreTx { +func NewTx(key, value string, accAddress sdk.AccAddress) *kvstoreTx { bytes := fmt.Sprintf("%s=%s", key, value) return &kvstoreTx{ - key: []byte(key), - value: []byte(value), - bytes: []byte(bytes), + key: []byte(key), + value: []byte(value), + bytes: []byte(bytes), + address: accAddress, } } @@ -55,6 +109,8 @@ func (tx *kvstoreTx) GetSigners() []sdk.AccAddress { return nil } +func (tx *kvstoreTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubKeys not implemented") } + // takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has // all the signatures and can be used to authenticate. func decodeTx(txBytes []byte) (sdk.Tx, error) { @@ -63,10 +119,10 @@ func decodeTx(txBytes []byte) (sdk.Tx, error) { split := bytes.Split(txBytes, []byte("=")) if len(split) == 1 { //nolint:gocritic k := split[0] - tx = &kvstoreTx{k, k, txBytes} + tx = &kvstoreTx{k, k, txBytes, nil} } else if len(split) == 2 { k, v := split[0], split[1] - tx = &kvstoreTx{k, v, txBytes} + tx = &kvstoreTx{k, v, txBytes, nil} } else { return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "too many '='") } diff --git a/server/types/app.go b/server/types/app.go index b8cedff4fb9e..8eb941c57b12 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -57,7 +57,7 @@ type ( // RegisterNodeService registers the node gRPC Query service. RegisterNodeService(client.Context) - // Return the multistore instance + // CommitMultiStore return the multistore instance CommitMultiStore() sdk.CommitMultiStore } diff --git a/simapp/app_legacy.go b/simapp/app_legacy.go index 5cdc60d8fff5..a7956291ecb6 100644 --- a/simapp/app_legacy.go +++ b/simapp/app_legacy.go @@ -231,6 +231,7 @@ func NewSimApp( bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) keys := sdk.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, diff --git a/types/abci.go b/types/abci.go index 8f71362eda6d..e08cbe449f5d 100644 --- a/types/abci.go +++ b/types/abci.go @@ -19,3 +19,6 @@ type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBloc // PeerFilter responds to p2p filtering queries from Tendermint type PeerFilter func(info string) abci.ResponseQuery + +// ProcessProposalHandler defines a function type alias for processing a proposer +type ProcessProposalHandler func(ctx Context, proposal abci.RequestProcessProposal) abci.ResponseProcessProposal diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index f32150d6f41e..b2aa54a86762 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -3,39 +3,34 @@ package mempool import ( "errors" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// Tx defines an app-side mempool transaction interface that is as -// minimal as possible, only requiring applications to define the size of the -// transaction to be used when inserting, selecting, and deleting the transaction. -// Interface type casting can be used in the actual app-side mempool implementation. -type Tx interface { - types.Tx - - // Size returns the size of the transaction in bytes. - Size() int64 -} - type Mempool interface { // Insert attempts to insert a Tx into the app-side mempool returning // an error upon failure. - Insert(types.Context, Tx) error + Insert(sdk.Context, sdk.Tx) error - // Select returns the next set of available transactions from the app-side - // mempool, up to maxBytes or until the mempool is empty. The application can - // decide to return transactions from its own mempool, from the incoming - // txs, or some combination of both. - Select(txs [][]byte, maxBytes int64) ([]Tx, error) + // Select returns an Iterator over the app-side mempool. If txs are specified, then they shall be incorporated + // into the Iterator. The Iterator must be closed by the caller. + Select(sdk.Context, [][]byte) Iterator // CountTx returns the number of transactions currently in the mempool. CountTx() int // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. - Remove(Tx) error + Remove(sdk.Tx) error } -var ErrTxNotFound = errors.New("tx not found in mempool") +// Iterator defines an app-side mempool iterator interface that is as minimal as possible. The order of iteration +// is determined by the app-side mempool implementation. +type Iterator interface { + // Next returns the next transaction from the mempool. If there are no more transactions, it returns nil. + Next() Iterator -type Factory func() Mempool + // Tx returns the transaction at the current position of the iterator. + Tx() sdk.Tx +} + +var ErrTxNotFound = errors.New("tx not found in mempool") diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 38944a501cf8..44a9103d524d 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -13,9 +13,12 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" ) // testPubKey is a dummy implementation of PubKey used for testing. @@ -63,13 +66,10 @@ func (tx testTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { var ( _ sdk.Tx = (*testTx)(nil) - _ mempool.Tx = (*testTx)(nil) _ signing.SigVerifiableTx = (*testTx)(nil) _ cryptotypes.PubKey = (*testPubKey)(nil) ) -func (tx testTx) Size() int64 { return 1 } - func (tx testTx) GetMsgs() []sdk.Msg { return nil } func (tx testTx) ValidateBasic() error { return nil } @@ -105,6 +105,23 @@ func (tx txSpec) String() string { return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) } +func fetchTxs(iterator mempool.Iterator, maxBytes int64) []sdk.Tx { + const txSize = 1 + var ( + txs []sdk.Tx + numBytes int64 + ) + for iterator != nil { + if numBytes += txSize; numBytes > maxBytes { + break + } + txs = append(txs, iterator.Tx()) + i := iterator.Next() + iterator = i + } + return txs +} + func (s *MempoolTestSuite) TestDefaultMempool() { t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) @@ -121,6 +138,11 @@ func (s *MempoolTestSuite) TestDefaultMempool() { txs = append(txs, tx) } + // empty mempool behavior + require.Equal(t, 0, s.mempool.CountTx()) + itr := s.mempool.Select(ctx, nil) + require.Nil(t, itr) + // same sender-nonce just overwrites a tx for _, tx := range txs { ctx = ctx.WithPriority(tx.priority) @@ -138,8 +160,8 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - sel, err := s.mempool.Select(nil, 13) - require.NoError(t, err) + itr = s.mempool.Select(ctx, nil) + sel := fetchTxs(itr, 13) require.Equal(t, 13, len(sel)) // a tx which does not implement SigVerifiableTx should not be inserted @@ -191,3 +213,28 @@ func (s *MempoolTestSuite) SetupTest() { func TestMempoolTestSuite(t *testing.T) { suite.Run(t, new(MempoolTestSuite)) } + +func (s *MempoolTestSuite) TestSampleTxs() { + ctxt := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + t := s.T() + s.resetMempool() + mp := s.mempool + delegatorTx, err := unmarshalTx(msgWithdrawDelegatorReward) + + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, delegatorTx)) + require.Equal(t, 1, mp.CountTx()) + + proposalTx, err := unmarshalTx(msgMultiSigMsgSubmitProposal) + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, proposalTx)) + require.Equal(t, 2, mp.CountTx()) +} + +func unmarshalTx(txBytes []byte) (sdk.Tx, error) { + cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) + return cfg.TxConfig.TxJSONDecoder()(txBytes) +} + +var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") +var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index da7f10d8d193..a8a2f0413c5e 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -9,6 +9,11 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" ) +var ( + _ Mempool = (*nonceMempool)(nil) + _ Iterator = (*nonceMempoolIterator)(nil) +) + // nonceMempool is a mempool that keeps transactions sorted by nonce. Transactions with the lowest nonce globally // are prioritized. Transactions with the same nonce are prioritized by sender address. Fee/gas based // prioritization is not supported. @@ -16,6 +21,24 @@ type nonceMempool struct { txQueue *huandu.SkipList } +type nonceMempoolIterator struct { + currentTx *huandu.Element +} + +func (i nonceMempoolIterator) Next() Iterator { + if i.currentTx == nil { + return nil + } else if n := i.currentTx.Next(); n != nil { + return nonceMempoolIterator{currentTx: n} + } else { + return nil + } +} + +func (i nonceMempoolIterator) Tx() sdk.Tx { + return i.currentTx.Value.(sdk.Tx) +} + type txKey struct { nonce uint64 sender string @@ -34,6 +57,7 @@ func txKeyLessNonce(a, b any) int { return huandu.String.Compare(keyB.sender, keyA.sender) } +// NewNonceMempool creates a new mempool that prioritizes transactions by nonce, the lowest first. func NewNonceMempool() Mempool { sp := &nonceMempool{ txQueue: huandu.New(huandu.LessThanFunc(txKeyLessNonce)), @@ -44,7 +68,7 @@ func NewNonceMempool() Mempool { // Insert adds a tx to the mempool. It returns an error if the tx does not have at least one signer. // priority is ignored. -func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { +func (sp nonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err @@ -61,26 +85,15 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { return nil } -// Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned -// in nonce order. -func (sp nonceMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var ( - txBytes int64 - selectedTxs []Tx - ) - +// Select returns an iterator ordering transactions the mempool with the lowest nonce globally first. A sender's txs +// will always be returned in nonce order. +func (sp nonceMempool) Select(_ sdk.Context, _ [][]byte) Iterator { currentTx := sp.txQueue.Front() - for currentTx != nil { - mempoolTx := currentTx.Value.(Tx) - - if txBytes += mempoolTx.Size(); txBytes <= maxBytes { - selectedTxs = append(selectedTxs, mempoolTx) - } else { - return selectedTxs, nil - } - currentTx = currentTx.Next() + if currentTx == nil { + return nil } - return selectedTxs, nil + + return &nonceMempoolIterator{currentTx: currentTx} } // CountTx returns the number of txs in the mempool. @@ -90,7 +103,7 @@ func (sp nonceMempool) CountTx() int { // Remove removes a tx from the mempool. It returns an error if the tx does not have at least one signer or the tx // was not found in the pool. -func (sp nonceMempool) Remove(tx Tx) error { +func (sp nonceMempool) Remove(tx sdk.Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err diff --git a/types/mempool/nonce_test.go b/types/mempool/nonce_test.go index e152ce0990dd..663a17c770de 100644 --- a/types/mempool/nonce_test.go +++ b/types/mempool/nonce_test.go @@ -193,8 +193,8 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - orderedTxs, err := pool.Select(nil, 1000) - require.NoError(t, err) + itr := pool.Select(ctx, nil) + orderedTxs := fetchTxs(itr, 1000) var txOrder []int for _, tx := range orderedTxs { txOrder = append(txOrder, tx.(testTx).id) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 604123167034..2bee58151cff 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -9,7 +9,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/ante" @@ -32,8 +31,6 @@ type wrapper struct { authInfoBz []byte txBodyHasUnknownNonCriticals bool - - txSize int64 } var ( @@ -43,7 +40,6 @@ var ( _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} - _ mempool.Tx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -66,12 +62,6 @@ func newBuilder(cdc codec.Codec) *wrapper { } } -// Size returns the size of the transaction, but is only correct immediately after decoding a proto-marshal transaction. -// It should not be used in any other cases. -func (w *wrapper) Size() int64 { - return w.txSize -} - func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } @@ -199,10 +189,12 @@ func (w *wrapper) GetSignaturesV2() ([]signing.SignatureV2, error) { if err != nil { return nil, err } + // sequence number is functionally a transaction nonce and referred to as such in the SDK + nonce := si.GetSequence() res[i] = signing.SignatureV2{ PubKey: pubKeys[i], Data: sigData, - Sequence: si.GetSequence(), + Sequence: nonce, } } @@ -221,8 +213,6 @@ func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 return nil } @@ -233,8 +223,6 @@ func (w *wrapper) SetTimeoutHeight(height uint64) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetMemo(memo string) { @@ -242,8 +230,6 @@ func (w *wrapper) SetMemo(memo string) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetGasLimit(limit uint64) { @@ -255,8 +241,6 @@ func (w *wrapper) SetGasLimit(limit uint64) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetFeeAmount(coins sdk.Coins) { @@ -268,8 +252,6 @@ func (w *wrapper) SetFeeAmount(coins sdk.Coins) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetTip(tip *tx.Tip) { @@ -277,8 +259,6 @@ func (w *wrapper) SetTip(tip *tx.Tip) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { @@ -290,8 +270,6 @@ func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { @@ -303,8 +281,6 @@ func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error { @@ -336,8 +312,6 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.tx.AuthInfo.SignerInfos = infos // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { @@ -348,8 +322,6 @@ func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { w.tx.AuthInfo.SignerInfos[index] = info // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) setSignatures(sigs [][]byte) { @@ -396,15 +368,11 @@ func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any { func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.ExtensionOptions = extOpts w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.NonCriticalExtensionOptions = extOpts w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error { diff --git a/x/auth/tx/module/module.go b/x/auth/tx/module/module.go index a051a3fb4c7b..f1f4dd82a84b 100644 --- a/x/auth/tx/module/module.go +++ b/x/auth/tx/module/module.go @@ -79,8 +79,9 @@ func ProvideModule(in TxInputs) TxOutputs { app.SetPostHandler(postHandler) } - // TxDecoder + // TxDecoder/TxEncoder app.SetTxDecoder(txConfig.TxDecoder()) + app.SetTxEncoder(txConfig.TxEncoder()) } return TxOutputs{TxConfig: txConfig, BaseAppOption: baseAppOption} diff --git a/x/distribution/types/codec.go b/x/distribution/types/codec.go index 5c5b21f0b401..9d5118a938f8 100644 --- a/x/distribution/types/codec.go +++ b/x/distribution/types/codec.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" govcodec "github.com/cosmos/cosmos-sdk/x/gov/codec" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" groupcodec "github.com/cosmos/cosmos-sdk/x/group/codec" ) @@ -37,6 +38,10 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { &MsgCommunityPoolSpend{}, ) + registry.RegisterImplementations( + (*govtypes.Content)(nil), + &CommunityPoolSpendProposal{}) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/distribution/types/proposal.go b/x/distribution/types/proposal.go index 925738d3086a..84dce599f7cc 100644 --- a/x/distribution/types/proposal.go +++ b/x/distribution/types/proposal.go @@ -3,8 +3,38 @@ package types import ( "fmt" "strings" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" ) +// GetTitle returns the title of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetTitle() string { return csp.Title } + +// GetDescription returns the description of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetDescription() string { return csp.Description } + +// GetDescription returns the routing key of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalType() string { return "CommunityPoolSpend" } + +// ValidateBasic runs basic stateless validity checks +func (csp *CommunityPoolSpendProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(csp) + if err != nil { + return err + } + if !csp.Amount.IsValid() { + return ErrInvalidProposalAmount + } + if csp.Recipient == "" { + return ErrEmptyProposalRecipient + } + + return nil +} + // String implements the Stringer interface. func (csp CommunityPoolSpendProposal) String() string { var b strings.Builder diff --git a/x/genutil/gentx.go b/x/genutil/gentx.go index f7e4250def1d..1598ccd603a5 100644 --- a/x/genutil/gentx.go +++ b/x/genutil/gentx.go @@ -107,7 +107,7 @@ func DeliverGenTxs( res := deliverTx(abci.RequestDeliverTx{Tx: bz}) if !res.IsOK() { - return nil, fmt.Errorf("failed to execute DelverTx for '%s': %s", genTx, res.Log) + return nil, fmt.Errorf("failed to execute DeliverTx for '%s': %s", genTx, res.Log) } }