feat!: Comet v0.38 Integration (#15519)

Co-authored-by: marbar3778 <marbar3778@yahoo.com>
Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com>
Co-authored-by: Aaron Craelius <aaron@regen.network>
Co-authored-by: Matt Kocubinski <mkocubinski@gmail.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
Aleksandr Bezobchuk
2023-05-24 16:09:19 +00:00
committed by GitHub
co-authored by marbar3778 cool-developer Aaron Craelius Matt Kocubinski Julien Robert
parent 166be3766b
commit 6cee22df52
148 changed files with 14747 additions and 15262 deletions
+625 -521
View File
File diff suppressed because it is too large Load Diff
+237 -552
View File
File diff suppressed because it is too large Load Diff
+281
View File
@@ -0,0 +1,281 @@
package baseapp
import (
"bytes"
"fmt"
"cosmossdk.io/math"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
cmtcrypto "github.com/cometbft/cometbft/crypto"
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
protoio "github.com/cosmos/gogoproto/io"
"github.com/cosmos/gogoproto/proto"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/mempool"
)
// VoteExtensionThreshold defines the total voting power % that must be
// submitted in order for all vote extensions to be considered valid for a
// given height.
var VoteExtensionThreshold = math.LegacyNewDecWithPrec(667, 3)
type (
// Validator defines the interface contract require for verifying vote extension
// signatures. Typically, this will be implemented by the x/staking module,
// which has knowledge of the CometBFT public key.
Validator interface {
CmtConsPublicKey() (cmtprotocrypto.PublicKey, error)
BondedTokens() math.Int
}
// ValidatorStore defines the interface contract require for verifying vote
// extension signatures. Typically, this will be implemented by the x/staking
// module, which has knowledge of the CometBFT public key.
ValidatorStore interface {
GetValidatorByConsAddr(sdk.Context, cryptotypes.Address) (Validator, error)
TotalBondedTokens(ctx sdk.Context) math.Int
}
)
// ValidateVoteExtensions defines a helper function for verifying vote extension
// signatures that may be passed or manually injected into a block proposal from
// a proposer in ProcessProposal. It returns an error if any signature is invalid
// or if unexpected vote extensions and/or signatures are found or less than 2/3
// power is received.
func ValidateVoteExtensions(
ctx sdk.Context,
valStore ValidatorStore,
currentHeight int64,
chainID string,
extCommit abci.ExtendedCommitInfo,
) error {
cp := ctx.ConsensusParams()
extsEnabled := cp.Abci != nil && cp.Abci.VoteExtensionsEnableHeight > 0
marshalDelimitedFn := func(msg proto.Message) ([]byte, error) {
var buf bytes.Buffer
if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var sumVP math.Int
for _, vote := range extCommit.Votes {
if !extsEnabled {
if len(vote.VoteExtension) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension at height %d", currentHeight)
}
if len(vote.ExtensionSignature) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension signature at height %d", currentHeight)
}
continue
}
if len(vote.ExtensionSignature) == 0 {
return fmt.Errorf("vote extensions enabled; received empty vote extension signature at height %d", currentHeight)
}
valConsAddr := cmtcrypto.Address(vote.Validator.Address)
validator, err := valStore.GetValidatorByConsAddr(ctx, valConsAddr)
if err != nil {
return fmt.Errorf("failed to get validator %X: %w", valConsAddr, err)
}
if validator == nil {
return fmt.Errorf("validator %X not found", valConsAddr)
}
cmtPubKeyProto, err := validator.CmtConsPublicKey()
if err != nil {
return fmt.Errorf("failed to get validator %X public key: %w", valConsAddr, err)
}
cmtPubKey, err := cryptoenc.PubKeyFromProto(cmtPubKeyProto)
if err != nil {
return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err)
}
cve := cmtproto.CanonicalVoteExtension{
Extension: vote.VoteExtension,
Height: currentHeight - 1, // the vote extension was signed in the previous height
Round: int64(extCommit.Round),
ChainId: chainID,
}
extSignBytes, err := marshalDelimitedFn(&cve)
if err != nil {
return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err)
}
if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
return fmt.Errorf("failed to verify validator %X vote extension signature", valConsAddr)
}
sumVP = sumVP.Add(validator.BondedTokens())
}
// Ensure we have at least 2/3 voting power that submitted valid vote
// extensions.
totalVP := valStore.TotalBondedTokens(ctx)
percentSubmitted := math.LegacyNewDecFromInt(sumVP).Quo(math.LegacyNewDecFromInt(totalVP))
if percentSubmitted.LT(VoteExtensionThreshold) {
return fmt.Errorf("insufficient cumulative voting power received to verify vote extensions; got: %s, expected: >=%s", percentSubmitted, VoteExtensionThreshold)
}
return nil
}
type (
// ProposalTxVerifier defines the interface that is implemented by BaseApp,
// that any custom ABCI PrepareProposal and ProcessProposal handler can use
// to verify a transaction.
ProposalTxVerifier interface {
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error)
}
// DefaultProposalHandler defines the default ABCI PrepareProposal and
// ProcessProposal handlers.
DefaultProposalHandler struct {
mempool mempool.Mempool
txVerifier ProposalTxVerifier
}
)
func NewDefaultProposalHandler(mp mempool.Mempool, txVerifier ProposalTxVerifier) DefaultProposalHandler {
return DefaultProposalHandler{
mempool: mp,
txVerifier: txVerifier,
}
}
// PrepareProposalHandler returns the default implementation for processing an
// ABCI proposal. The application's mempool is enumerated and all valid
// transactions are added to the proposal. Transactions are valid if they:
//
// 1) Successfully encode to bytes.
// 2) Are valid (i.e. pass runTx, AnteHandler only).
//
// Enumeration is halted once RequestPrepareProposal.MaxBytes of transactions is
// reached or the mempool is exhausted.
//
// Note:
//
// - Step (2) is identical to the validation step performed in
// DefaultProcessProposal. It is very important that the same validation logic
// is used in both steps, and applications must ensure that this is the case in
// non-default handlers.
//
// - If no mempool is set or if the mempool is a no-op mempool, the transactions
// requested from CometBFT will simply be returned, which, by default, are in
// FIFO order.
func (h DefaultProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
// If the mempool is nil or a no-op mempool, we simply return the transactions
// requested from CometBFT, which, by default, should be in FIFO order.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil
}
var (
selectedTxs [][]byte
totalTxBytes int64
)
iterator := h.mempool.Select(ctx, req.Txs)
for iterator != nil {
memTx := iterator.Tx()
// NOTE: Since transaction verification was already executed in CheckTx,
// which calls mempool.Insert, in theory everything in the pool should be
// valid. But some mempool implementations may insert invalid txs, so we
// check again.
bz, err := h.txVerifier.PrepareProposalVerifyTx(memTx)
if err != nil {
err := h.mempool.Remove(memTx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
panic(err)
}
} else {
txSize := int64(len(bz))
if totalTxBytes += txSize; totalTxBytes <= req.MaxTxBytes {
selectedTxs = append(selectedTxs, bz)
} else {
// We've reached capacity per req.MaxTxBytes so we cannot select any
// more transactions.
break
}
}
iterator = iterator.Next()
}
return &abci.ResponsePrepareProposal{Txs: selectedTxs}, nil
}
}
// ProcessProposalHandler 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.
// Note that step (2) is identical to the validation step performed in
// DefaultPrepareProposal. It is very important that the same validation logic
// is used in both steps, and applications must ensure that this is the case in
// non-default handlers.
func (h DefaultProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
for _, txBytes := range req.Txs {
_, err := h.txVerifier.ProcessProposalVerifyTx(txBytes)
if err != nil {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil
}
}
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
}
}
// NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always
// return the transactions sent by the client's request.
func NoOpPrepareProposal() sdk.PrepareProposalHandler {
return func(_ sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil
}
}
// NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always
// return ACCEPT.
func NoOpProcessProposal() sdk.ProcessProposalHandler {
return func(_ sdk.Context, _ *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
}
}
// NoOpExtendVote defines a no-op ExtendVote handler. It will always return an
// empty byte slice as the vote extension.
func NoOpExtendVote() sdk.ExtendVoteHandler {
return func(_ sdk.Context, _ *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
return &abci.ResponseExtendVote{VoteExtension: []byte{}}, nil
}
}
// NoOpVerifyVoteExtensionHandler defines a no-op VerifyVoteExtension handler. It
// will always return an ACCEPT status with no error.
func NoOpVerifyVoteExtensionHandler() sdk.VerifyVoteExtensionHandler {
return func(_ sdk.Context, _ *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil
}
}
+234 -221
View File
@@ -1,6 +1,7 @@
package baseapp
import (
"context"
"fmt"
"sort"
"strconv"
@@ -20,32 +21,35 @@ import (
"golang.org/x/exp/maps"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"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"
)
type (
// Enum mode for app.runTx
runTxMode uint8
execMode uint8
// StoreLoader defines a customizable function to control how we load the CommitMultiStore
// from disk. This is useful for state migration, when loading a datastore written with
// an older version of the software. In particular, if a module changed the substore key name
// (or removed a substore) between two versions of the software.
// StoreLoader defines a customizable function to control how we load the
// CommitMultiStore from disk. This is useful for state migration, when
// loading a datastore written with an older version of the software. In
// particular, if a module changed the substore key name (or removed a substore)
// between two versions of the software.
StoreLoader func(ms storetypes.CommitMultiStore) error
)
const (
runTxModeCheck runTxMode = iota // Check a transaction
runTxModeReCheck // Recheck a (pending) transaction after a commit
runTxModeSimulate // Simulate a transaction
runTxModeDeliver // Deliver a transaction
runTxPrepareProposal // Prepare a TM block proposal
runTxProcessProposal // Process a TM block proposal
execModeCheck execMode = iota // Check a transaction
execModeReCheck // Recheck a (pending) transaction after a commit
execModeSimulate // Simulate a transaction
execModePrepareProposal // Prepare a block proposal
execModeProcessProposal // Process a block proposal
execModeVoteExtension // Extend or verify a pre-commit vote
execModeFinalize // Finalize a block proposal
)
var _ abci.Application = (*BaseApp)(nil)
var _ servertypes.ABCI = (*BaseApp)(nil)
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
@@ -62,33 +66,59 @@ type BaseApp struct {
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.PostHandler // 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
prepareProposal sdk.PrepareProposalHandler // the handler which runs on ABCI PrepareProposal
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
prepareCheckStater sdk.PrepareCheckStater // logic to run during commit using the checkState
precommiter sdk.Precommiter // logic to run during commit using the deliverState
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.PostHandler // post handler, optional, e.g. for tips
initChainer sdk.InitChainer // ABCI InitChain handler
beginBlocker sdk.BeginBlocker // (legacy ABCI) BeginBlock handler
endBlocker sdk.EndBlocker // (legacy ABCI) EndBlock handler
processProposal sdk.ProcessProposalHandler // ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler // ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler // ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler // ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater // logic to run during commit using the checkState
precommiter sdk.Precommiter // logic to run during commit using the deliverState
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
// volatile states:
//
// 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
processProposalState *state // for ProcessProposal
prepareProposalState *state // for PrepareProposal
// - checkState is set on InitChain and reset on Commit
// - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
// on Commit.
//
// - checkState: Used for CheckTx, which is set based on the previous block's
// state. This state is never committed.
//
// - prepareProposalState: Used for PrepareProposal, which is set based on the
// previous block's state. This state is never committed. In case of multiple
// consensus rounds, the state is always reset to the previous block's state.
//
// - voteExtensionState: Used for ExtendVote and VerifyVoteExtension, which is
// set based on the previous block's state. This state is never committed. In
// case of multiple rounds, the state is always reset to the previous block's
// state.
//
// - processProposalState: Used for ProcessProposal, which is set based on the
// the previous block's state. This state is never committed. In case of
// multiple rounds, the state is always reset to the previous block's state.
//
// - finalizeBlockState: Used for FinalizeBlock, which is set based on the
// previous block's state. This state is committed.
checkState *state
prepareProposalState *state
processProposalState *state
voteExtensionState *state
finalizeBlockState *state
// an inter-block write-through cache provided to the context during deliverState
// An inter-block write-through cache provided to the context during the ABCI
// FinalizeBlock call.
interBlockCache storetypes.MultiStorePersistentCache
// paramStore is used to query for ABCI consensus parameters from an
@@ -99,7 +129,7 @@ type BaseApp struct {
// transaction. This is mainly used for DoS and spam prevention.
minGasPrices sdk.DecCoins
// initialHeight is the initial height at which we start the baseapp
// initialHeight is the initial height at which we start the BaseApp
initialHeight int64
// flag for sealing options and parameters to a BaseApp
@@ -149,8 +179,6 @@ type BaseApp struct {
// NewBaseApp returns a reference to an initialized BaseApp. It accepts a
// variadic number of option functions, which act on the BaseApp to set
// configuration choices.
//
// NOTE: The db is used to store the version number for now.
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
@@ -179,11 +207,15 @@ func NewBaseApp(
if app.prepareProposal == nil {
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}
if app.processProposal == nil {
app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler())
}
if app.extendVote == nil {
app.SetExtendVoteHandler(NoOpExtendVote())
}
if app.verifyVoteExt == nil {
app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler())
}
if app.interBlockCache != nil {
app.cms.SetInterBlockCache(app.interBlockCache)
}
@@ -342,6 +374,16 @@ func (app *BaseApp) LastBlockHeight() int64 {
return app.cms.LastCommitID().Version
}
// ChainID returns the chainID of the app.
func (app *BaseApp) ChainID() string {
return app.chainID
}
// AnteHandler returns the AnteHandler of the app.
func (app *BaseApp) AnteHandler() sdk.AnteHandler {
return app.anteHandler
}
// Init initializes the app. It seals the app, preventing any
// further modifications. In addition, it validates the app against
// the earlier provided settings. Returns an error if validation fails.
@@ -354,7 +396,7 @@ func (app *BaseApp) Init() error {
emptyHeader := cmtproto.Header{ChainID: app.chainID}
// needed for the export command which inits from store but never calls initchain
app.setState(runTxModeCheck, emptyHeader)
app.setState(execModeCheck, emptyHeader)
app.Seal()
if app.cms == nil {
@@ -405,7 +447,7 @@ func (app *BaseApp) IsSealed() bool { return app.sealed }
// setState sets the BaseApp's state for the corresponding mode with a branched
// multi-store (i.e. a CacheMultiStore) and a new Context with the same
// multi-store branch, and provided header.
func (app *BaseApp) setState(mode runTxMode, header cmtproto.Header) {
func (app *BaseApp) setState(mode execMode, header cmtproto.Header) {
ms := app.cms.CacheMultiStore()
baseState := &state{
ms: ms,
@@ -413,24 +455,39 @@ func (app *BaseApp) setState(mode runTxMode, header cmtproto.Header) {
}
switch mode {
case runTxModeCheck:
// Minimum gas prices are also set. It is set on InitChain and reset on Commit.
case execModeCheck:
baseState.ctx = baseState.ctx.WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices)
app.checkState = baseState
case runTxModeDeliver:
// It is set on InitChain and BeginBlock and set to nil on Commit.
app.deliverState = baseState
case runTxPrepareProposal:
// It is set on InitChain and Commit.
case execModePrepareProposal:
app.prepareProposalState = baseState
case runTxProcessProposal:
// It is set on InitChain and Commit.
case execModeProcessProposal:
app.processProposalState = baseState
case execModeVoteExtension:
app.voteExtensionState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState
default:
panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
}
}
// GetFinalizeBlockStateCtx returns the Context associated with the FinalizeBlock
// state. This Context can be used to write data derived from processing vote
// extensions to application state during ProcessProposal.
//
// NOTE:
// - Do NOT use or write to state using this Context unless you intend for
// that state to be committed.
// - Do NOT use or write to state using this Context on the first block.
func (app *BaseApp) GetFinalizeBlockStateCtx() sdk.Context {
return app.finalizeBlockState.ctx
}
// SetCircuitBreaker sets the circuit breaker for the BaseApp.
// The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
func (app *BaseApp) SetCircuitBreaker(cb CircuitBreaker) {
@@ -452,15 +509,17 @@ func (app *BaseApp) GetConsensusParams(ctx sdk.Context) cmtproto.ConsensusParams
return cp
}
// StoreConsensusParams sets the consensus parameters to the baseapp's param store.
// StoreConsensusParams sets the consensus parameters to the BaseApp's param
// store.
//
// NOTE: We're explicitly not storing the CometBFT app_version in the param store.
// It's stored instead in the x/upgrade store, with its own bump logic.
func (app *BaseApp) StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams) error {
if app.paramStore == nil {
panic("cannot store consensus params with no params store set")
}
return app.paramStore.Set(ctx, cp)
// We're explicitly not storing the CometBFT app_version in the param store. It's
// stored instead in the x/upgrade store, with its own bump logic.
}
// AddRunTxRecoveryHandler adds custom app.runTx method panic handlers.
@@ -493,9 +552,9 @@ func (app *BaseApp) GetMaximumBlockGas(ctx sdk.Context) uint64 {
}
}
func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error {
if req.Header.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Header.Height)
func (app *BaseApp) validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock) error {
if req.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Height)
}
lastBlockHeight := app.LastBlockHeight()
@@ -515,8 +574,8 @@ func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error {
expectedHeight = lastBlockHeight + 1
}
if req.Header.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Header.Height, expectedHeight)
if req.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight)
}
return nil
@@ -542,18 +601,15 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error {
return nil
}
// Returns the application's deliverState if app is in runTxModeDeliver,
// prepareProposalState if app is in runTxPrepareProposal, processProposalState
// if app is in runTxProcessProposal, and checkState otherwise.
func (app *BaseApp) getState(mode runTxMode) *state {
func (app *BaseApp) getState(mode execMode) *state {
switch mode {
case runTxModeDeliver:
return app.deliverState
case execModeFinalize:
return app.finalizeBlockState
case runTxPrepareProposal:
case execModePrepareProposal:
return app.prepareProposalState
case runTxProcessProposal:
case execModeProcessProposal:
return app.processProposalState
default:
@@ -570,7 +626,7 @@ func (app *BaseApp) getBlockGasMeter(ctx sdk.Context) storetypes.GasMeter {
}
// retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context {
func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context {
modeState := app.getState(mode)
if modeState == nil {
panic(fmt.Sprintf("state is nil for mode %v", mode))
@@ -581,11 +637,11 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
if mode == runTxModeReCheck {
if mode == execModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
}
if mode == runTxModeSimulate {
if mode == execModeSimulate {
ctx, _ = ctx.CacheContext()
}
@@ -611,6 +667,95 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context
return ctx.WithMultiStore(msCache), msCache
}
func (app *BaseApp) beginBlock(req *abci.RequestFinalizeBlock) sdk.BeginBlock {
var (
resp sdk.BeginBlock
err error
)
if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.ctx)
if err != nil {
panic(err)
}
// append BeginBlock attributes to all events in the EndBlock response
for i, event := range resp.Events {
resp.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{Key: "mode", Value: "BeginBlock"},
)
}
resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents)
}
return resp
}
func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult {
gInfo := sdk.GasInfo{}
resultStr := "successful"
var resp *abci.ExecTxResult
defer func() {
telemetry.IncrCounter(1, "tx", "count")
telemetry.IncrCounter(1, "tx", resultStr)
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx)
if err != nil {
resultStr = "failed"
resp = sdkerrors.ResponseExecTxResultWithEvents(
err,
gInfo.GasWanted,
gInfo.GasUsed,
sdk.MarkEventsToIndex(anteEvents, app.indexEvents),
app.trace,
)
return resp
}
resp = &abci.ExecTxResult{
GasWanted: int64(gInfo.GasWanted),
GasUsed: int64(gInfo.GasUsed),
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
return resp
}
// endBlock is an application-defined function that is called after transactions
// have been processed in FinalizeBlock.
func (app *BaseApp) endBlock(ctx context.Context) (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.endBlocker != nil {
eb, err := app.endBlocker(app.finalizeBlockState.ctx)
if err != nil {
panic(err)
}
// append EndBlock attributes to all events in the EndBlock response
for i, event := range eb.Events {
eb.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{Key: "mode", Value: "EndBlock"},
)
}
eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents)
endblock = eb
}
return endblock, nil
}
// runTx processes a transaction within a given execution mode, encoded transaction
// bytes, and the decoded transaction itself. All state transitions occur through
// a cached Context depending on the mode provided. State only gets persisted
@@ -618,7 +763,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context
// Note, gas execution info is always returned. A reference to a Result is
// returned if the tx does not run out of gas and if all the messages are valid
// and execute successfully. An error is returned otherwise.
func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, priority int64, err error) {
func (app *BaseApp) runTx(mode execMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
// NOTE: GasWanted should be returned by the AnteHandler. GasUsed is
// determined by the GasMeter. We need access to the context to get the gas
// meter, so we initialize upfront.
@@ -628,8 +773,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
ms := ctx.MultiStore()
// only run the tx if there is block gas remaining
if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, 0, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
if mode == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
defer func() {
@@ -661,18 +806,18 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
// NOTE: consumeBlockGas must exist in a separate defer function from the
// general deferred recovery function to recover from consumeBlockGas as it'll
// be executed first (deferred statements are executed as stack).
if mode == runTxModeDeliver {
if mode == execModeFinalize {
defer consumeBlockGas()
}
tx, err := app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{}, nil, nil, 0, err
return sdk.GasInfo{}, nil, nil, err
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{}, nil, nil, 0, err
return sdk.GasInfo{}, nil, nil, err
}
if app.anteHandler != nil {
@@ -690,7 +835,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
// performance benefits, but it'll be more difficult to get right.
anteCtx, msCache = app.cacheTxContext(ctx, txBytes)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate)
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
if !newCtx.IsZero() {
// At this point, newCtx.MultiStore() is a store branch, or something else
@@ -708,23 +853,22 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
gasWanted = ctx.GasMeter().Limit()
if err != nil {
return gInfo, nil, nil, 0, err
return gInfo, nil, nil, err
}
priority = ctx.Priority()
msCache.Write()
anteEvents = events.ToABCIEvents()
}
if mode == runTxModeCheck {
if mode == execModeCheck {
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, priority, err
return gInfo, nil, anteEvents, err
}
} else if mode == runTxModeDeliver {
} else if mode == execModeFinalize {
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents, priority,
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
}
}
@@ -748,28 +892,28 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
// Note that the state is still preserved.
postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.postHandler(postCtx, tx, mode == runTxModeSimulate, err == nil)
newCtx, err := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil)
if err != nil {
return gInfo, nil, anteEvents, priority, err
return gInfo, nil, anteEvents, err
}
result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...)
}
if mode == runTxModeDeliver {
if mode == execModeFinalize {
// When block gas exceeds, it'll panic and won't commit the cached store.
consumeBlockGas()
msCache.Write()
}
if len(anteEvents) > 0 && (mode == runTxModeDeliver || mode == runTxModeSimulate) {
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
// append the events in the order of occurrence
result.Events = append(anteEvents, result.Events...)
}
}
return gInfo, result, anteEvents, priority, err
return gInfo, result, anteEvents, err
}
// runMsgs iterates through a list of messages and executes them with the provided
@@ -777,13 +921,13 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
// and DeliverTx. An error is returned if any single message fails or if a
// Handler does not exist for a given message route. Otherwise, a reference to a
// Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*sdk.Result, error) {
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode execMode) (*sdk.Result, error) {
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
// NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter.
for i, msg := range msgs {
if mode != runTxModeDeliver && mode != runTxModeSimulate {
if mode != execModeFinalize && mode != execModeSimulate {
break
}
@@ -874,7 +1018,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
return nil, err
}
_, _, _, _, err = app.runTx(runTxPrepareProposal, bz)
_, _, _, err = app.runTx(execModePrepareProposal, bz)
if err != nil {
return nil, err
}
@@ -893,7 +1037,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
return nil, err
}
_, _, _, _, err = app.runTx(runTxProcessProposal, txBz)
_, _, _, err = app.runTx(execModeProcessProposal, txBz)
if err != nil {
return nil, err
}
@@ -901,137 +1045,6 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
return tx, nil
}
type (
// ProposalTxVerifier defines the interface that is implemented by BaseApp,
// that any custom ABCI PrepareProposal and ProcessProposal handler can use
// to verify a transaction.
ProposalTxVerifier interface {
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error)
}
// DefaultProposalHandler defines the default ABCI PrepareProposal and
// ProcessProposal handlers.
DefaultProposalHandler struct {
mempool mempool.Mempool
txVerifier ProposalTxVerifier
}
)
func NewDefaultProposalHandler(mp mempool.Mempool, txVerifier ProposalTxVerifier) DefaultProposalHandler {
return DefaultProposalHandler{
mempool: mp,
txVerifier: txVerifier,
}
}
// PrepareProposalHandler returns the default implementation for processing an
// ABCI proposal. The application's mempool is enumerated and all valid
// transactions are added to the proposal. Transactions are valid if they:
//
// 1) Successfully encode to bytes.
// 2) Are valid (i.e. pass runTx, AnteHandler only).
//
// Enumeration is halted once RequestPrepareProposal.MaxBytes of transactions is
// reached or the mempool is exhausted.
//
// Note:
//
// - Step (2) is identical to the validation step performed in
// DefaultProcessProposal. It is very important that the same validation logic
// is used in both steps, and applications must ensure that this is the case in
// non-default handlers.
//
// - If no mempool is set or if the mempool is a no-op mempool, the transactions
// requested from CometBFT will simply be returned, which, by default, are in
// FIFO order.
func (h DefaultProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
// If the mempool is nil or a no-op mempool, we simply return the transactions
// requested from CometBFT, which, by default, should be in FIFO order.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
return abci.ResponsePrepareProposal{Txs: req.Txs}
}
var (
selectedTxs [][]byte
totalTxBytes int64
)
iterator := h.mempool.Select(ctx, req.Txs)
for iterator != nil {
memTx := iterator.Tx()
// NOTE: Since transaction verification was already executed in CheckTx,
// which calls mempool.Insert, in theory everything in the pool should be
// valid. But some mempool implementations may insert invalid txs, so we
// check again.
bz, err := h.txVerifier.PrepareProposalVerifyTx(memTx)
if err != nil {
err := h.mempool.Remove(memTx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
panic(err)
}
} else {
txSize := int64(len(bz))
if totalTxBytes += txSize; totalTxBytes <= req.MaxTxBytes {
selectedTxs = append(selectedTxs, bz)
} else {
// We've reached capacity per req.MaxTxBytes so we cannot select any
// more transactions.
break
}
}
iterator = iterator.Next()
}
return abci.ResponsePrepareProposal{Txs: selectedTxs}
}
}
// ProcessProposalHandler 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.
// Note that step (2) is identical to the validation step performed in
// DefaultPrepareProposal. It is very important that the same validation logic
// is used in both steps, and applications must ensure that this is the case in
// non-default handlers.
func (h DefaultProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal {
for _, txBytes := range req.Txs {
_, err := h.txVerifier.ProcessProposalVerifyTx(txBytes)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
}
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
}
}
// NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always
// return the transactions sent by the client's request.
func NoOpPrepareProposal() sdk.PrepareProposalHandler {
return func(_ sdk.Context, req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
return abci.ResponsePrepareProposal{Txs: req.Txs}
}
}
// NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always
// return ACCEPT.
func NoOpProcessProposal() sdk.ProcessProposalHandler {
return func(_ sdk.Context, _ abci.RequestProcessProposal) abci.ResponseProcessProposal {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
}
}
// Close is called in start cmd to gracefully cleanup resources.
func (app *BaseApp) Close() error {
return nil
+68 -53
View File
@@ -1,6 +1,7 @@
package baseapp_test
import (
"context"
"fmt"
"math/rand"
"testing"
@@ -97,7 +98,7 @@ func NewBaseAppSuiteWithSnapshots(t *testing.T, cfg SnapshotsConfig, opts ...fun
baseapptestutil.RegisterKeyValueServer(suite.baseApp.MsgServiceRouter(), MsgKeyValueImpl{})
suite.baseApp.InitChain(abci.RequestInitChain{
suite.baseApp.InitChain(&abci.RequestInitChain{
ConsensusParams: &cmtproto.ConsensusParams{},
})
@@ -105,8 +106,8 @@ func NewBaseAppSuiteWithSnapshots(t *testing.T, cfg SnapshotsConfig, opts ...fun
keyCounter := 0
for height := int64(1); height <= int64(cfg.blocks); height++ {
suite.baseApp.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: height}})
txs := [][]byte{}
for txNum := 0; txNum < cfg.blockTxs; txNum++ {
msgs := []sdk.Msg{}
for msgNum := 0; msgNum < 100; msgNum++ {
@@ -127,12 +128,17 @@ func NewBaseAppSuiteWithSnapshots(t *testing.T, cfg SnapshotsConfig, opts ...fun
txBytes, err := suite.txConfig.TxEncoder()(builder.GetTx())
require.NoError(t, err)
resp := suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.True(t, resp.IsOK(), "%v", resp.String())
txs = append(txs, txBytes)
}
suite.baseApp.EndBlock(abci.RequestEndBlock{Height: height})
suite.baseApp.Commit()
_, err := suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{
Height: height,
Txs: txs,
})
require.NoError(t, err)
_, err = suite.baseApp.Commit()
require.NoError(t, err)
// wait for snapshot to be taken, since it happens asynchronously
if cfg.snapshotInterval > 0 && uint64(height)%cfg.snapshotInterval == 0 {
@@ -177,16 +183,18 @@ func TestLoadVersion(t *testing.T) {
require.Equal(t, emptyCommitID, lastID)
// execute a block, collect commit ID
header := cmtproto.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID1 := storetypes.CommitID{Version: 1, Hash: res.Data}
res, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
commitID1 := storetypes.CommitID{Version: 1, Hash: res.AppHash}
_, err = app.Commit()
require.NoError(t, err)
// execute a block, collect commit ID
header = cmtproto.Header{Height: 2}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID2 := storetypes.CommitID{Version: 2, Hash: res.Data}
res, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
commitID2 := storetypes.CommitID{Version: 2, Hash: res.AppHash}
_, err = app.Commit()
require.NoError(t, err)
// reload with LoadLatestVersion
app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt)
@@ -205,8 +213,10 @@ func TestLoadVersion(t *testing.T) {
testLoadVersionHelper(t, app, int64(1), commitID1)
app.BeginBlock(abci.RequestBeginBlock{Header: header})
app.Commit()
_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
testLoadVersionHelper(t, app, int64(2), commitID2)
}
@@ -289,9 +299,11 @@ func TestSetLoader(t *testing.T) {
require.Nil(t, err)
// "execute" one block
app.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: 2}})
res := app.Commit()
require.NotNil(t, res.Data)
res, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
require.NoError(t, err)
require.NotNil(t, res.AppHash)
_, err = app.Commit()
require.NoError(t, err)
// check db is properly updated
checkStore(t, db, 2, tc.loadStoreKey, k, v)
@@ -307,7 +319,8 @@ func TestVersionSetterGetter(t *testing.T) {
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil, pruningOpt)
require.Equal(t, "", app.Version())
res := app.Query(abci.RequestQuery{Path: "app/version"})
res, err := app.Query(context.TODO(), &abci.RequestQuery{Path: "app/version"})
require.NoError(t, err)
require.True(t, res.IsOK())
require.Equal(t, "", string(res.Value))
@@ -315,7 +328,8 @@ func TestVersionSetterGetter(t *testing.T) {
app.SetVersion(versionString)
require.Equal(t, versionString, app.Version())
res = app.Query(abci.RequestQuery{Path: "app/version"})
res, err = app.Query(context.TODO(), &abci.RequestQuery{Path: "app/version"})
require.NoError(t, err)
require.True(t, res.IsOK())
require.Equal(t, versionString, string(res.Value))
}
@@ -334,10 +348,11 @@ func TestLoadVersionInvalid(t *testing.T) {
err = app.LoadVersion(-1)
require.Error(t, err)
header := cmtproto.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID1 := storetypes.CommitID{Version: 1, Hash: res.Data}
res, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
commitID1 := storetypes.CommitID{Version: 1, Hash: res.AppHash}
_, err = app.Commit()
require.NoError(t, err)
// create a new app with the stores mounted under the same cap key
app = baseapp.NewBaseApp(name, logger, db, nil, pruningOpt)
@@ -437,13 +452,10 @@ func TestCustomRunTxPanicHandler(t *testing.T) {
}
suite := NewBaseAppSuite(t, anteOpt)
suite.baseApp.InitChain(abci.RequestInitChain{
suite.baseApp.InitChain(&abci.RequestInitChain{
ConsensusParams: &cmtproto.ConsensusParams{},
})
header := cmtproto.Header{Height: 1}
suite.baseApp.BeginBlock(abci.RequestBeginBlock{Header: header})
suite.baseApp.AddRunTxRecoveryHandler(func(recoveryObj interface{}) error {
err, ok := recoveryObj.(error)
if !ok {
@@ -462,7 +474,9 @@ func TestCustomRunTxPanicHandler(t *testing.T) {
tx := newTxCounter(t, suite.txConfig, 0, 0)
require.PanicsWithValue(t, customPanicMsg, func() {
suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), tx)
bz, err := suite.txConfig.TxEncoder()(tx)
require.NoError(t, err)
suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{bz}})
})
}
}
@@ -477,13 +491,10 @@ func TestBaseAppAnteHandler(t *testing.T) {
deliverKey := []byte("deliver-key")
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey})
suite.baseApp.InitChain(abci.RequestInitChain{
suite.baseApp.InitChain(&abci.RequestInitChain{
ConsensusParams: &cmtproto.ConsensusParams{},
})
header := cmtproto.Header{Height: suite.baseApp.LastBlockHeight() + 1}
suite.baseApp.BeginBlock(abci.RequestBeginBlock{Header: header})
// execute a tx that will fail ante handler execution
//
// NOTE: State should not be mutated here. This will be implicitly checked by
@@ -494,11 +505,12 @@ func TestBaseAppAnteHandler(t *testing.T) {
txBytes, err := suite.txConfig.TxEncoder()(tx)
require.NoError(t, err)
res := suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
res, err := suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
require.NoError(t, err)
require.Empty(t, res.Events)
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
require.False(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res))
ctx := getDeliverStateCtx(suite.baseApp)
ctx := getFinalizeBlockStateCtx(suite.baseApp)
store := ctx.KVStore(capKey1)
require.Equal(t, int64(0), getIntFromStore(t, store, anteKey))
@@ -510,11 +522,12 @@ func TestBaseAppAnteHandler(t *testing.T) {
txBytes, err = suite.txConfig.TxEncoder()(tx)
require.NoError(t, err)
res = suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.NotEmpty(t, res.Events)
require.False(t, res.IsOK(), fmt.Sprintf("%v", res))
res, err = suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
require.NoError(t, err)
require.Empty(t, res.Events)
require.False(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res))
ctx = getDeliverStateCtx(suite.baseApp)
ctx = getFinalizeBlockStateCtx(suite.baseApp)
store = ctx.KVStore(capKey1)
require.Equal(t, int64(1), getIntFromStore(t, store, anteKey))
require.Equal(t, int64(0), getIntFromStore(t, store, deliverKey))
@@ -526,16 +539,16 @@ func TestBaseAppAnteHandler(t *testing.T) {
txBytes, err = suite.txConfig.TxEncoder()(tx)
require.NoError(t, err)
res = suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.NotEmpty(t, res.Events)
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
res, err = suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
require.NoError(t, err)
require.NotEmpty(t, res.TxResults[0].Events)
require.True(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res))
ctx = getDeliverStateCtx(suite.baseApp)
ctx = getFinalizeBlockStateCtx(suite.baseApp)
store = ctx.KVStore(capKey1)
require.Equal(t, int64(2), getIntFromStore(t, store, anteKey))
require.Equal(t, int64(1), getIntFromStore(t, store, deliverKey))
suite.baseApp.EndBlock(abci.RequestEndBlock{})
suite.baseApp.Commit()
}
@@ -550,10 +563,10 @@ func TestABCI_CreateQueryContext(t *testing.T) {
name := t.Name()
app := baseapp.NewBaseApp(name, log.NewTestLogger(t), db, nil)
app.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: 1}})
app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
app.Commit()
app.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: 2}})
app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 2})
app.Commit()
testCases := []struct {
@@ -590,8 +603,8 @@ func TestSetMinGasPrices(t *testing.T) {
func TestGetMaximumBlockGas(t *testing.T) {
suite := NewBaseAppSuite(t)
suite.baseApp.InitChain(abci.RequestInitChain{})
ctx := suite.baseApp.NewContext(true, cmtproto.Header{})
suite.baseApp.InitChain(&abci.RequestInitChain{})
ctx := suite.baseApp.NewContext(true, cmtproto.Header{}) // TODO remove header here
suite.baseApp.StoreConsensusParams(ctx, cmtproto.ConsensusParams{Block: &cmtproto.BlockParams{MaxGas: 0}})
require.Equal(t, uint64(0), suite.baseApp.GetMaximumBlockGas(ctx))
@@ -634,9 +647,11 @@ func TestLoadVersionPruning(t *testing.T) {
// Commit seven blocks, of which 7 (latest) is kept in addition to 6, 5
// (keep recent) and 3 (keep every).
for i := int64(1); i <= 7; i++ {
app.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: i}})
res := app.Commit()
lastCommitID = storetypes.CommitID{Version: i, Hash: res.Data}
res, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: i})
require.NoError(t, err)
_, err = app.Commit()
require.NoError(t, err)
lastCommitID = storetypes.CommitID{Version: i, Hash: res.AppHash}
}
for _, v := range []int64{1, 2, 4} {
+7 -7
View File
@@ -118,7 +118,7 @@ func TestBaseApp_BlockGas(t *testing.T) {
genState := GenesisStateWithSingleValidator(t, cdc, appBuilder)
stateBytes, err := cmtjson.MarshalIndent(genState, "", " ")
require.NoError(t, err)
bapp.InitChain(abci.RequestInitChain{
bapp.InitChain(&abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{},
ConsensusParams: simtestutil.DefaultConsensusParams,
AppStateBytes: stateBytes,
@@ -158,22 +158,22 @@ func TestBaseApp_BlockGas(t *testing.T) {
_, txBytes, err := createTestTx(txConfig, txBuilder, privs, accNums, accSeqs, ctx.ChainID())
require.NoError(t, err)
bapp.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: 1}})
rsp := bapp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
rsp, err := bapp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
require.NoError(t, err)
// check result
ctx = bapp.GetContextForDeliverTx(txBytes)
ctx = bapp.GetContextForFinalizeBlock(txBytes)
okValue := ctx.KVStore(bapp.UnsafeFindStoreKey(banktypes.ModuleName)).Get([]byte("ok"))
if tc.expErr {
if tc.panicTx {
require.Equal(t, sdkerrors.ErrPanic.ABCICode(), rsp.Code)
require.Equal(t, sdkerrors.ErrPanic.ABCICode(), rsp.TxResults[0].Code)
} else {
require.Equal(t, sdkerrors.ErrOutOfGas.ABCICode(), rsp.Code)
require.Equal(t, sdkerrors.ErrOutOfGas.ABCICode(), rsp.TxResults[0].Code)
}
require.Empty(t, okValue)
} else {
require.Equal(t, uint32(0), rsp.Code)
require.Equal(t, uint32(0), rsp.TxResults[0].Code)
require.Equal(t, []byte("ok"), okValue)
}
// check block gas is always consumed
+1 -1
View File
@@ -12,7 +12,7 @@ var _ genesis.TxHandler = (*BaseApp)(nil)
// ExecuteGenesisTx implements genesis.GenesisState from
// cosmossdk.io/core/genesis to set initial state in genesis
func (ba BaseApp) ExecuteGenesisTx(tx []byte) error {
res := ba.DeliverTx(types.RequestDeliverTx{Tx: tx})
res := ba.deliverTx(tx)
if res.Code != types.CodeTypeOK {
return errors.New(res.Log)
+5 -5
View File
@@ -38,7 +38,7 @@ func NewGRPCQueryRouter() *GRPCQueryRouter {
// GRPCQueryHandler defines a function type which handles ABCI Query requests
// using gRPC
type GRPCQueryHandler = func(ctx sdk.Context, req abci.RequestQuery) (abci.ResponseQuery, error)
type GRPCQueryHandler = func(ctx sdk.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error)
// Route returns the GRPCQueryHandler for a given query route path or nil
// if not found
@@ -76,25 +76,25 @@ func (qrt *GRPCQueryRouter) RegisterService(sd *grpc.ServiceDesc, handler interf
)
}
qrt.routes[fqName] = func(ctx sdk.Context, req abci.RequestQuery) (abci.ResponseQuery, error) {
qrt.routes[fqName] = func(ctx sdk.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) {
// call the method handler from the service description with the handler object,
// a wrapped sdk.Context with proto-unmarshaled data from the ABCI request data
res, err := methodHandler(handler, ctx, func(i interface{}) error {
return qrt.cdc.Unmarshal(req.Data, i)
}, nil)
if err != nil {
return abci.ResponseQuery{}, err
return nil, err
}
// proto marshal the result bytes
var resBytes []byte
resBytes, err = qrt.cdc.Marshal(res)
if err != nil {
return abci.ResponseQuery{}, err
return nil, err
}
// return the result bytes as the response value
return abci.ResponseQuery{
return &abci.ResponseQuery{
Height: req.Height,
Value: resBytes,
}, nil
+1 -1
View File
@@ -45,7 +45,7 @@ func (q *QueryServiceTestHelper) Invoke(_ gocontext.Context, method string, args
return err
}
res, err := querier(q.Ctx, abci.RequestQuery{Data: reqBz})
res, err := querier(q.Ctx, &abci.RequestQuery{Data: reqBz})
if err != nil {
return err
}
+5 -5
View File
@@ -83,8 +83,8 @@ type voteInfoWrapper struct {
var _ comet.VoteInfo = (*voteInfoWrapper)(nil)
func (v voteInfoWrapper) SignedLastBlock() bool {
return v.VoteInfo.SignedLastBlock
func (v voteInfoWrapper) GetBlockIDFlag() comet.BlockIDFlag {
return comet.BlockIDFlag(v.VoteInfo.BlockIdFlag)
}
func (v voteInfoWrapper) Validator() comet.Validator {
@@ -131,7 +131,7 @@ func (m misbehaviorWrapper) TotalVotingPower() int64 {
}
type prepareProposalInfo struct {
abci.RequestPrepareProposal
*abci.RequestPrepareProposal
}
var _ comet.BlockInfo = (*prepareProposalInfo)(nil)
@@ -188,8 +188,8 @@ type extendedVoteInfoWrapper struct {
var _ comet.VoteInfo = (*extendedVoteInfoWrapper)(nil)
func (e extendedVoteInfoWrapper) SignedLastBlock() bool {
return e.ExtendedVoteInfo.SignedLastBlock
func (e extendedVoteInfoWrapper) GetBlockIDFlag() comet.BlockIDFlag {
return comet.BlockIDFlag(e.ExtendedVoteInfo.BlockIdFlag)
}
func (e extendedVoteInfoWrapper) Validator() comet.Validator {
+4 -4
View File
@@ -5,7 +5,6 @@ import (
"testing"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
@@ -116,7 +115,7 @@ func TestMsgService(t *testing.T) {
app.MsgServiceRouter(),
testdata.MsgServerImpl{},
)
_ = app.BeginBlock(abci.RequestBeginBlock{Header: cmtproto.Header{Height: 1}})
app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
msg := testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}}
@@ -157,6 +156,7 @@ func TestMsgService(t *testing.T) {
// Send the tx to the app
txBytes, err := txConfig.TxEncoder()(txBuilder.GetTx())
require.NoError(t, err)
res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.Equal(t, abci.CodeTypeOK, res.Code, "res=%+v", res)
res, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
require.NoError(t, err)
require.Equal(t, abci.CodeTypeOK, res.TxResults[0].Code, "res=%+v", res)
}
+16
View File
@@ -300,6 +300,22 @@ func (app *BaseApp) SetPrepareProposal(handler sdk.PrepareProposalHandler) {
app.prepareProposal = handler
}
func (app *BaseApp) SetExtendVoteHandler(handler sdk.ExtendVoteHandler) {
if app.sealed {
panic("SetExtendVoteHandler() on sealed BaseApp")
}
app.extendVote = handler
}
func (app *BaseApp) SetVerifyVoteExtensionHandler(handler sdk.VerifyVoteExtensionHandler) {
if app.sealed {
panic("SetVerifyVoteExtensionHandler() on sealed BaseApp")
}
app.verifyVoteExt = handler
}
// SetStoreMetrics sets the prepare proposal function for the BaseApp.
func (app *BaseApp) SetStoreMetrics(gatherer metrics.StoreMetrics) {
if app.sealed {
+347
View File
@@ -0,0 +1,347 @@
package baseapp_test
import (
"context"
"fmt"
"testing"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/stretchr/testify/require"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
func TestABCI_ListSnapshots(t *testing.T) {
ssCfg := SnapshotsConfig{
blocks: 5,
blockTxs: 4,
snapshotInterval: 2,
snapshotKeepRecent: 2,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
}
suite := NewBaseAppSuiteWithSnapshots(t, ssCfg)
resp, err := suite.baseApp.ListSnapshots(&abci.RequestListSnapshots{})
require.NoError(t, err)
for _, s := range resp.Snapshots {
require.NotEmpty(t, s.Hash)
require.NotEmpty(t, s.Metadata)
s.Hash = nil
s.Metadata = nil
}
require.Equal(t, &abci.ResponseListSnapshots{Snapshots: []*abci.Snapshot{
{Height: 4, Format: snapshottypes.CurrentFormat, Chunks: 2},
{Height: 2, Format: snapshottypes.CurrentFormat, Chunks: 1},
}}, resp)
}
func TestABCI_SnapshotWithPruning(t *testing.T) {
testCases := map[string]struct {
ssCfg SnapshotsConfig
expectedSnapshots []*abci.Snapshot
}{
"prune nothing with snapshot": {
ssCfg: SnapshotsConfig{
blocks: 20,
blockTxs: 2,
snapshotInterval: 5,
snapshotKeepRecent: 1,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
},
expectedSnapshots: []*abci.Snapshot{
{Height: 20, Format: snapshottypes.CurrentFormat, Chunks: 5},
},
},
"prune everything with snapshot": {
ssCfg: SnapshotsConfig{
blocks: 20,
blockTxs: 2,
snapshotInterval: 5,
snapshotKeepRecent: 1,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningEverything),
},
expectedSnapshots: []*abci.Snapshot{
{Height: 20, Format: snapshottypes.CurrentFormat, Chunks: 5},
},
},
"default pruning with snapshot": {
ssCfg: SnapshotsConfig{
blocks: 20,
blockTxs: 2,
snapshotInterval: 5,
snapshotKeepRecent: 1,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningDefault),
},
expectedSnapshots: []*abci.Snapshot{
{Height: 20, Format: snapshottypes.CurrentFormat, Chunks: 5},
},
},
"custom": {
ssCfg: SnapshotsConfig{
blocks: 25,
blockTxs: 2,
snapshotInterval: 5,
snapshotKeepRecent: 2,
pruningOpts: pruningtypes.NewCustomPruningOptions(12, 12),
},
expectedSnapshots: []*abci.Snapshot{
{Height: 25, Format: snapshottypes.CurrentFormat, Chunks: 6},
{Height: 20, Format: snapshottypes.CurrentFormat, Chunks: 5},
},
},
"no snapshots": {
ssCfg: SnapshotsConfig{
blocks: 10,
blockTxs: 2,
snapshotInterval: 0, // 0 implies disable snapshots
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
},
expectedSnapshots: []*abci.Snapshot{},
},
"keep all snapshots": {
ssCfg: SnapshotsConfig{
blocks: 10,
blockTxs: 2,
snapshotInterval: 3,
snapshotKeepRecent: 0, // 0 implies keep all snapshots
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
},
expectedSnapshots: []*abci.Snapshot{
{Height: 9, Format: snapshottypes.CurrentFormat, Chunks: 2},
{Height: 6, Format: snapshottypes.CurrentFormat, Chunks: 2},
{Height: 3, Format: snapshottypes.CurrentFormat, Chunks: 1},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
suite := NewBaseAppSuiteWithSnapshots(t, tc.ssCfg)
resp, err := suite.baseApp.ListSnapshots(&abci.RequestListSnapshots{})
require.NoError(t, err)
for _, s := range resp.Snapshots {
require.NotEmpty(t, s.Hash)
require.NotEmpty(t, s.Metadata)
s.Hash = nil
s.Metadata = nil
}
require.Equal(t, &abci.ResponseListSnapshots{Snapshots: tc.expectedSnapshots}, resp)
// Validate that heights were pruned correctly by querying the state at the last height that should be present relative to latest
// and the first height that should be pruned.
//
// Exceptions:
// * Prune nothing: should be able to query all heights (we only test first and latest)
// * Prune default: should be able to query all heights (we only test first and latest)
// * The reason for default behaving this way is that we only commit 20 heights but default has 100_000 keep-recent
var lastExistingHeight int64
if tc.ssCfg.pruningOpts.GetPruningStrategy() == pruningtypes.PruningNothing || tc.ssCfg.pruningOpts.GetPruningStrategy() == pruningtypes.PruningDefault {
lastExistingHeight = 1
} else {
// Integer division rounds down so by multiplying back we get the last height at which we pruned
lastExistingHeight = int64((tc.ssCfg.blocks/tc.ssCfg.pruningOpts.Interval)*tc.ssCfg.pruningOpts.Interval - tc.ssCfg.pruningOpts.KeepRecent)
}
// Query 1
res, err := suite.baseApp.Query(context.TODO(), &abci.RequestQuery{Path: fmt.Sprintf("/store/%s/key", capKey2.Name()), Data: []byte("0"), Height: lastExistingHeight})
require.NoError(t, err)
require.NotNil(t, res, "height: %d", lastExistingHeight)
require.NotNil(t, res.Value, "height: %d", lastExistingHeight)
// Query 2
res, err = suite.baseApp.Query(context.TODO(), &abci.RequestQuery{Path: fmt.Sprintf("/store/%s/key", capKey2.Name()), Data: []byte("0"), Height: lastExistingHeight - 1})
require.NoError(t, err)
require.NotNil(t, res, "height: %d", lastExistingHeight-1)
if tc.ssCfg.pruningOpts.GetPruningStrategy() == pruningtypes.PruningNothing || tc.ssCfg.pruningOpts.GetPruningStrategy() == pruningtypes.PruningDefault {
// With prune nothing or default, we query height 0 which translates to the latest height.
require.NotNil(t, res.Value, "height: %d", lastExistingHeight-1)
}
})
}
}
func TestABCI_LoadSnapshotChunk(t *testing.T) {
ssCfg := SnapshotsConfig{
blocks: 2,
blockTxs: 5,
snapshotInterval: 2,
snapshotKeepRecent: snapshottypes.CurrentFormat,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
}
suite := NewBaseAppSuiteWithSnapshots(t, ssCfg)
testCases := map[string]struct {
height uint64
format uint32
chunk uint32
expectEmpty bool
}{
"Existing snapshot": {2, snapshottypes.CurrentFormat, 1, false},
"Missing height": {100, snapshottypes.CurrentFormat, 1, true},
"Missing format": {2, snapshottypes.CurrentFormat + 1, 1, true},
"Missing chunk": {2, snapshottypes.CurrentFormat, 9, true},
"Zero height": {0, snapshottypes.CurrentFormat, 1, true},
"Zero format": {2, 0, 1, true},
"Zero chunk": {2, snapshottypes.CurrentFormat, 0, false},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
resp, _ := suite.baseApp.LoadSnapshotChunk(&abci.RequestLoadSnapshotChunk{
Height: tc.height,
Format: tc.format,
Chunk: tc.chunk,
})
if tc.expectEmpty {
require.Equal(t, &abci.ResponseLoadSnapshotChunk{}, resp)
return
}
require.NotEmpty(t, resp.Chunk)
})
}
}
func TestABCI_OfferSnapshot_Errors(t *testing.T) {
ssCfg := SnapshotsConfig{
blocks: 0,
blockTxs: 0,
snapshotInterval: 2,
snapshotKeepRecent: 2,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
}
suite := NewBaseAppSuiteWithSnapshots(t, ssCfg)
m := snapshottypes.Metadata{ChunkHashes: [][]byte{{1}, {2}, {3}}}
metadata, err := m.Marshal()
require.NoError(t, err)
hash := []byte{1, 2, 3}
testCases := map[string]struct {
snapshot *abci.Snapshot
result abci.ResponseOfferSnapshot_Result
}{
"nil snapshot": {nil, abci.ResponseOfferSnapshot_REJECT},
"invalid format": {&abci.Snapshot{
Height: 1, Format: 9, Chunks: 3, Hash: hash, Metadata: metadata,
}, abci.ResponseOfferSnapshot_REJECT_FORMAT},
"incorrect chunk count": {&abci.Snapshot{
Height: 1, Format: snapshottypes.CurrentFormat, Chunks: 2, Hash: hash, Metadata: metadata,
}, abci.ResponseOfferSnapshot_REJECT},
"no chunks": {&abci.Snapshot{
Height: 1, Format: snapshottypes.CurrentFormat, Chunks: 0, Hash: hash, Metadata: metadata,
}, abci.ResponseOfferSnapshot_REJECT},
"invalid metadata serialization": {&abci.Snapshot{
Height: 1, Format: snapshottypes.CurrentFormat, Chunks: 0, Hash: hash, Metadata: []byte{3, 1, 4},
}, abci.ResponseOfferSnapshot_REJECT},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
resp, err := suite.baseApp.OfferSnapshot(&abci.RequestOfferSnapshot{Snapshot: tc.snapshot})
require.NoError(t, err)
require.Equal(t, tc.result, resp.Result)
})
}
// Offering a snapshot after one has been accepted should error
resp, err := suite.baseApp.OfferSnapshot(&abci.RequestOfferSnapshot{Snapshot: &abci.Snapshot{
Height: 1,
Format: snapshottypes.CurrentFormat,
Chunks: 3,
Hash: []byte{1, 2, 3},
Metadata: metadata,
}})
require.NoError(t, err)
require.Equal(t, &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, resp)
resp, err = suite.baseApp.OfferSnapshot(&abci.RequestOfferSnapshot{Snapshot: &abci.Snapshot{
Height: 2,
Format: snapshottypes.CurrentFormat,
Chunks: 3,
Hash: []byte{1, 2, 3},
Metadata: metadata,
}})
require.NoError(t, err)
require.Equal(t, &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, resp)
}
func TestABCI_ApplySnapshotChunk(t *testing.T) {
srcCfg := SnapshotsConfig{
blocks: 4,
blockTxs: 10,
snapshotInterval: 2,
snapshotKeepRecent: 2,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
}
srcSuite := NewBaseAppSuiteWithSnapshots(t, srcCfg)
targetCfg := SnapshotsConfig{
blocks: 0,
blockTxs: 0,
snapshotInterval: 2,
snapshotKeepRecent: 2,
pruningOpts: pruningtypes.NewPruningOptions(pruningtypes.PruningNothing),
}
targetSuite := NewBaseAppSuiteWithSnapshots(t, targetCfg)
// fetch latest snapshot to restore
respList, err := srcSuite.baseApp.ListSnapshots(&abci.RequestListSnapshots{})
require.NoError(t, err)
require.NotEmpty(t, respList.Snapshots)
snapshot := respList.Snapshots[0]
// make sure the snapshot has at least 3 chunks
require.GreaterOrEqual(t, snapshot.Chunks, uint32(3), "Not enough snapshot chunks")
// begin a snapshot restoration in the target
respOffer, err := targetSuite.baseApp.OfferSnapshot(&abci.RequestOfferSnapshot{Snapshot: snapshot})
require.NoError(t, err)
require.Equal(t, &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, respOffer)
// We should be able to pass an invalid chunk and get a verify failure, before
// reapplying it.
respApply, err := targetSuite.baseApp.ApplySnapshotChunk(&abci.RequestApplySnapshotChunk{
Index: 0,
Chunk: []byte{9},
Sender: "sender",
})
require.NoError(t, err)
require.Equal(t, &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_RETRY,
RefetchChunks: []uint32{0},
RejectSenders: []string{"sender"},
}, respApply)
// fetch each chunk from the source and apply it to the target
for index := uint32(0); index < snapshot.Chunks; index++ {
respChunk, err := srcSuite.baseApp.LoadSnapshotChunk(&abci.RequestLoadSnapshotChunk{
Height: snapshot.Height,
Format: snapshot.Format,
Chunk: index,
})
require.NoError(t, err)
require.NotNil(t, respChunk.Chunk)
respApply, err := targetSuite.baseApp.ApplySnapshotChunk(&abci.RequestApplySnapshotChunk{
Index: index,
Chunk: respChunk.Chunk,
})
require.NoError(t, err)
require.Equal(t, &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_ACCEPT,
}, respApply)
}
// the target should now have the same hash as the source
require.Equal(t, srcSuite.baseApp.LastCommitID(), targetSuite.baseApp.LastCommitID())
}
+29 -31
View File
@@ -5,14 +5,13 @@ import (
"fmt"
"testing"
storetypes "cosmossdk.io/store/types"
abci "github.com/cometbft/cometbft/abci/types"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/stretchr/testify/require"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp"
baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ storetypes.ABCIListener = (*MockABCIListener)(nil)
@@ -29,20 +28,12 @@ func NewMockABCIListener(name string) MockABCIListener {
}
}
func (m MockABCIListener) ListenBeginBlock(ctx context.Context, req abci.RequestBeginBlock, res abci.ResponseBeginBlock) error {
func (m MockABCIListener) ListenFinalizeBlock(_ context.Context, _ abci.RequestFinalizeBlock, _ abci.ResponseFinalizeBlock) error {
return nil
}
func (m MockABCIListener) ListenEndBlock(ctx context.Context, req abci.RequestEndBlock, res abci.ResponseEndBlock) error {
return nil
}
func (m MockABCIListener) ListenDeliverTx(ctx context.Context, req abci.RequestDeliverTx, res abci.ResponseDeliverTx) error {
return nil
}
func (m *MockABCIListener) ListenCommit(ctx context.Context, res abci.ResponseCommit, changeSet []*storetypes.StoreKVPair) error {
m.ChangeSet = changeSet
func (m *MockABCIListener) ListenCommit(_ context.Context, _ abci.ResponseCommit, cs []*storetypes.StoreKVPair) error {
m.ChangeSet = cs
return nil
}
@@ -59,9 +50,11 @@ func TestABCI_MultiListener_StateChanges(t *testing.T) {
addListenerOpt := func(bapp *baseapp.BaseApp) { bapp.CommitMultiStore().AddListeners([]storetypes.StoreKey{distKey1}) }
suite := NewBaseAppSuite(t, anteOpt, distOpt, streamingManagerOpt, addListenerOpt)
suite.baseApp.InitChain(abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
})
suite.baseApp.InitChain(
&abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
},
)
deliverKey := []byte("deliver-key")
baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey})
@@ -70,10 +63,14 @@ func TestABCI_MultiListener_StateChanges(t *testing.T) {
txPerHeight := 5
for blockN := 0; blockN < nBlocks; blockN++ {
header := tmproto.Header{Height: int64(blockN) + 1}
suite.baseApp.BeginBlock(abci.RequestBeginBlock{Header: header})
txs := [][]byte{}
var expectedChangeSet []*storetypes.StoreKVPair
// create final block context state
_, err := suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: int64(blockN) + 1, Txs: txs})
require.NoError(t, err)
for i := 0; i < txPerHeight; i++ {
counter := int64(blockN*txPerHeight + i)
tx := newTxCounter(t, suite.txConfig, counter, counter)
@@ -83,7 +80,7 @@ func TestABCI_MultiListener_StateChanges(t *testing.T) {
sKey := []byte(fmt.Sprintf("distKey%d", i))
sVal := []byte(fmt.Sprintf("distVal%d", i))
store := getDeliverStateCtx(suite.baseApp).KVStore(distKey1)
store := getFinalizeBlockStateCtx(suite.baseApp).KVStore(distKey1)
store.Set(sKey, sVal)
expectedChangeSet = append(expectedChangeSet, &storetypes.StoreKVPair{
@@ -93,16 +90,18 @@ func TestABCI_MultiListener_StateChanges(t *testing.T) {
Value: sVal,
})
res := suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))
txs = append(txs, txBytes)
}
events := res.GetEvents()
res, err := suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: int64(blockN) + 1, Txs: txs})
require.NoError(t, err)
for _, tx := range res.TxResults {
events := tx.GetEvents()
require.Len(t, events, 3, "should contain ante handler, message type and counter events respectively")
require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event")
require.Equal(t, sdk.MarkEventsToIndex(counterEvent(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0].Attributes[0], events[2].Attributes[0], "msg handler update counter event")
// require.Equal(t, sdk.MarkEventsToIndex(counterEvent("ante_handler", counter).ToABCIEvents(), map[string]struct{}{})[0], events[0], "ante handler event")
// require.Equal(t, sdk.MarkEventsToIndex(counterEvent(sdk.EventTypeMessage, counter).ToABCIEvents(), map[string]struct{}{})[0], events[2], "msg handler update counter event")
}
suite.baseApp.EndBlock(abci.RequestEndBlock{})
suite.baseApp.Commit()
require.Equal(t, expectedChangeSet, mockListener1.ChangeSet, "should contain the same changeSet")
@@ -119,11 +118,11 @@ func Test_Ctx_with_StreamingManager(t *testing.T) {
addListenerOpt := func(bapp *baseapp.BaseApp) { bapp.CommitMultiStore().AddListeners([]storetypes.StoreKey{distKey1}) }
suite := NewBaseAppSuite(t, streamingManagerOpt, addListenerOpt)
suite.baseApp.InitChain(abci.RequestInitChain{
suite.baseApp.InitChain(&abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
})
ctx := getDeliverStateCtx(suite.baseApp)
ctx := getFinalizeBlockStateCtx(suite.baseApp)
sm := ctx.StreamingManager()
require.NotNil(t, sm, fmt.Sprintf("nil StreamingManager: %v", sm))
require.Equal(t, listeners, sm.ABCIListeners, fmt.Sprintf("should contain same listeners: %v", listeners))
@@ -132,16 +131,15 @@ func Test_Ctx_with_StreamingManager(t *testing.T) {
nBlocks := 2
for blockN := 0; blockN < nBlocks; blockN++ {
header := tmproto.Header{Height: int64(blockN) + 1}
suite.baseApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := getDeliverStateCtx(suite.baseApp)
suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: int64(blockN) + 1})
ctx := getFinalizeBlockStateCtx(suite.baseApp)
sm := ctx.StreamingManager()
require.NotNil(t, sm, fmt.Sprintf("nil StreamingManager: %v", sm))
require.Equal(t, listeners, sm.ABCIListeners, fmt.Sprintf("should contain same listeners: %v", listeners))
require.Equal(t, true, sm.StopNodeOnErr, "should contain StopNodeOnErr = true")
suite.baseApp.EndBlock(abci.RequestEndBlock{})
suite.baseApp.Commit()
}
}
+20 -9
View File
@@ -18,13 +18,14 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *
if err != nil {
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
}
gasInfo, result, _, _, err := app.runTx(runTxModeCheck, bz)
gasInfo, result, _, err := app.runTx(execModeCheck, bz)
return gasInfo, result, err
}
// Simulate executes a tx in simulate mode to get result and gas info.
func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) {
gasInfo, result, _, _, err := app.runTx(runTxModeSimulate, txBytes)
gasInfo, result, _, err := app.runTx(execModeSimulate, txBytes)
return gasInfo, result, err
}
@@ -34,28 +35,38 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo,
if err != nil {
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
}
gasInfo, result, _, _, err := app.runTx(runTxModeDeliver, bz)
gasInfo, result, _, err := app.runTx(execModeFinalize, bz)
return gasInfo, result, err
}
// Context with current {check, deliver}State of the app used by tests.
func (app *BaseApp) NewContext(isCheckTx bool, header cmtproto.Header) sdk.Context {
func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) {
// See comment for Check().
bz, err := txEncoder(tx)
if err != nil {
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
}
gasInfo, result, _, err := app.runTx(execModeFinalize, bz)
return gasInfo, result, err
}
func (app *BaseApp) NewContext(isCheckTx bool, header cmtproto.Header) sdk.Context { // todo discuss how to remove header, wrapper or no
if isCheckTx {
return sdk.NewContext(app.checkState.ms, header, true, app.logger).
WithMinGasPrices(app.minGasPrices)
}
return sdk.NewContext(app.deliverState.ms, header, false, app.logger)
return sdk.NewContext(app.finalizeBlockState.ms, header, false, app.logger)
}
func (app *BaseApp) NewUncachedContext(isCheckTx bool, header cmtproto.Header) sdk.Context {
return sdk.NewContext(app.cms, header, isCheckTx, app.logger)
}
func (app *BaseApp) GetContextForDeliverTx(txBytes []byte) sdk.Context {
return app.getContextForTx(runTxModeDeliver, txBytes)
func (app *BaseApp) GetContextForFinalizeBlock(txBytes []byte) sdk.Context {
return app.getContextForTx(execModeFinalize, txBytes)
}
func (app *BaseApp) GetContextForCheckTx(txBytes []byte) sdk.Context {
return app.getContextForTx(runTxModeCheck, txBytes)
return app.getContextForTx(execModeCheck, txBytes)
}
+2 -2
View File
@@ -299,9 +299,9 @@ func getCheckStateCtx(app *baseapp.BaseApp) sdk.Context {
return rf.MethodByName("Context").Call(nil)[0].Interface().(sdk.Context)
}
func getDeliverStateCtx(app *baseapp.BaseApp) sdk.Context {
func getFinalizeBlockStateCtx(app *baseapp.BaseApp) sdk.Context {
v := reflect.ValueOf(app).Elem()
f := v.FieldByName("deliverState")
f := v.FieldByName("finalizeBlockState")
rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
return rf.MethodByName("Context").Call(nil)[0].Interface().(sdk.Context)
}