feat: ABCI 1.0 baseapp integration (#13453)

This commit is contained in:
Matt Kocubinski 2022-11-09 09:50:27 -06:00 committed by GitHub
parent 90fd3e9f42
commit 61effe8260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 653 additions and 143 deletions

View File

@ -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

196
baseapp/abci_v1_test.go Normal file
View File

@ -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))
}

View File

@ -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
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
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.
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}
}
}

View File

@ -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 &params, 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)
}
}

View File

@ -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
}

View File

@ -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{}

View File

@ -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",
}),
},
},
},
})
}))
}

View File

@ -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{

View File

@ -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 '='")
}

View File

@ -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
}

View File

@ -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,

View File

@ -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

View File

@ -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
}
// 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
// Tx returns the transaction at the current position of the iterator.
Tx() sdk.Tx
}
var ErrTxNotFound = errors.New("tx not found in mempool")
type Factory func() Mempool

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}
}