refactor(baseapp): create checktx handler (#24069)
Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io> Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com>
This commit is contained in:
parent
d68d169a63
commit
4f445ed933
@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Features
|
||||
|
||||
* (baseapp) [#24069](https://github.com/cosmos/cosmos-sdk/pull/24069) Create CheckTxHandler to allow extending the logic of CheckTx.
|
||||
* (types) [#24093](https://github.com/cosmos/cosmos-sdk/pull/24093) Added a new method, `IsGT`, for `types.Coin`. This method is used to check if a `types.Coin` is greater than another `types.Coin`.
|
||||
* (client/keys) [#24071](https://github.com/cosmos/cosmos-sdk/pull/24071) Add support for importing hex key using standard input.
|
||||
* (types) [#23780](https://github.com/cosmos/cosmos-sdk/pull/23780) Add a ValueCodec for the math.Uint type that can be used in collections maps.
|
||||
|
||||
@ -351,18 +351,27 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er
|
||||
return nil, fmt.Errorf("unknown RequestCheckTx type: %s", req.Type)
|
||||
}
|
||||
|
||||
gInfo, result, anteEvents, err := app.runTx(mode, req.Tx)
|
||||
if err != nil {
|
||||
return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
|
||||
if app.checkTxHandler == nil {
|
||||
gInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
|
||||
if err != nil {
|
||||
return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
|
||||
}
|
||||
|
||||
return &abci.ResponseCheckTx{
|
||||
GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints?
|
||||
GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints?
|
||||
Log: result.Log,
|
||||
Data: result.Data,
|
||||
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &abci.ResponseCheckTx{
|
||||
GasWanted: int64(gInfo.GasWanted), // TODO: Should type accept unsigned ints?
|
||||
GasUsed: int64(gInfo.GasUsed), // TODO: Should type accept unsigned ints?
|
||||
Log: result.Log,
|
||||
Data: result.Data,
|
||||
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
|
||||
}, nil
|
||||
// Create wrapper to avoid users overriding the execution mode
|
||||
runTx := func(txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
|
||||
return app.runTx(mode, txBytes, tx)
|
||||
}
|
||||
|
||||
return app.checkTxHandler(runTx, req)
|
||||
}
|
||||
|
||||
// PrepareProposal implements the PrepareProposal ABCI method and returns a
|
||||
|
||||
@ -926,7 +926,7 @@ func TestABCI_InvalidTransaction(t *testing.T) {
|
||||
_, _, err := suite.baseApp.SimDeliver(suite.txConfig.TxEncoder(), tx)
|
||||
require.Error(t, err)
|
||||
space, code, _ := errorsmod.ABCIInfo(err, false)
|
||||
require.EqualValues(t, sdkerrors.ErrTxDecode.ABCICode(), code)
|
||||
require.EqualValues(t, sdkerrors.ErrUnknownRequest.ABCICode(), code)
|
||||
require.EqualValues(t, sdkerrors.ErrTxDecode.Codespace(), space)
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,7 @@ type BaseApp struct {
|
||||
anteHandler sdk.AnteHandler // ante handler for fee and auth
|
||||
postHandler sdk.PostHandler // post handler, optional
|
||||
|
||||
checkTxHandler sdk.CheckTxHandler // ABCI CheckTx handler
|
||||
initChainer sdk.InitChainer // ABCI InitChain handler
|
||||
preBlocker sdk.PreBlocker // logic to run before BeginBlocker
|
||||
beginBlocker sdk.BeginBlocker // (legacy ABCI) BeginBlock handler
|
||||
@ -693,7 +694,6 @@ func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context {
|
||||
// a branched multi-store.
|
||||
func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) {
|
||||
ms := ctx.MultiStore()
|
||||
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
|
||||
msCache := ms.CacheMultiStore()
|
||||
if msCache.TracingEnabled() {
|
||||
msCache = msCache.SetTracingContext(
|
||||
@ -769,7 +769,7 @@ func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult {
|
||||
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
|
||||
}()
|
||||
|
||||
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx)
|
||||
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil)
|
||||
if err != nil {
|
||||
resultStr = "failed"
|
||||
resp = sdkerrors.ResponseExecTxResultWithEvents(
|
||||
@ -826,7 +826,9 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) {
|
||||
// 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 execMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
|
||||
// both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
|
||||
// passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
|
||||
func (app *BaseApp) runTx(mode execMode, txBytes []byte, tx sdk.Tx) (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.
|
||||
@ -874,9 +876,12 @@ func (app *BaseApp) runTx(mode execMode, txBytes []byte) (gInfo sdk.GasInfo, res
|
||||
defer consumeBlockGas()
|
||||
}
|
||||
|
||||
tx, err := app.txDecoder(txBytes)
|
||||
if err != nil {
|
||||
return sdk.GasInfo{}, nil, nil, err
|
||||
// if the transaction is not decoded, decode it here
|
||||
if tx == nil {
|
||||
tx, err = app.txDecoder(txBytes)
|
||||
if err != nil {
|
||||
return sdk.GasInfo{GasUsed: 0, GasWanted: 0}, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
msgs := tx.GetMsgs()
|
||||
@ -1115,7 +1120,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, _, err = app.runTx(execModePrepareProposal, bz)
|
||||
_, _, _, err = app.runTx(execModePrepareProposal, bz, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1134,7 +1139,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, _, err = app.runTx(execModeProcessProposal, txBz)
|
||||
_, _, _, err = app.runTx(execModeProcessProposal, txBz, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -350,6 +350,15 @@ func (app *BaseApp) SetPrepareProposal(handler sdk.PrepareProposalHandler) {
|
||||
app.prepareProposal = handler
|
||||
}
|
||||
|
||||
// SetCheckTx sets the checkTx function for the BaseApp.
|
||||
func (app *BaseApp) SetCheckTxHandler(handler sdk.CheckTxHandler) {
|
||||
if app.sealed {
|
||||
panic("SetCheckTxHandler() on sealed BaseApp")
|
||||
}
|
||||
|
||||
app.checkTxHandler = handler
|
||||
}
|
||||
|
||||
func (app *BaseApp) SetExtendVoteHandler(handler sdk.ExtendVoteHandler) {
|
||||
if app.sealed {
|
||||
panic("SetExtendVoteHandler() on sealed BaseApp")
|
||||
|
||||
@ -19,13 +19,13 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *
|
||||
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
|
||||
}
|
||||
|
||||
gasInfo, result, _, err := app.runTx(execModeCheck, bz)
|
||||
gasInfo, result, _, err := app.runTx(execModeCheck, bz, tx)
|
||||
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(execModeSimulate, txBytes)
|
||||
gasInfo, result, _, err := app.runTx(execModeSimulate, txBytes, nil)
|
||||
return gasInfo, result, err
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo,
|
||||
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
|
||||
}
|
||||
|
||||
gasInfo, result, _, err := app.runTx(execModeFinalize, bz)
|
||||
gasInfo, result, _, err := app.runTx(execModeFinalize, bz, tx)
|
||||
return gasInfo, result, err
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.
|
||||
return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err)
|
||||
}
|
||||
|
||||
gasInfo, result, _, err := app.runTx(execModeFinalize, bz)
|
||||
gasInfo, result, _, err := app.runTx(execModeFinalize, bz, tx)
|
||||
return gasInfo, result, err
|
||||
}
|
||||
|
||||
|
||||
50
docs/docs/build/abci/04-checktx.md
vendored
Normal file
50
docs/docs/build/abci/04-checktx.md
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
# CheckTx
|
||||
|
||||
CheckTx is called by the `BaseApp` when comet receives a transaction from a client, over the p2p network or RPC. The CheckTx method is responsible for validating the transaction and returning an error if the transaction is invalid.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph SDK[Cosmos SDK]
|
||||
B[Baseapp]
|
||||
A[AnteHandlers]
|
||||
B <-->|Validate TX| A
|
||||
end
|
||||
C[CometBFT] <-->|CheckTx|SDK
|
||||
U((User)) -->|Submit TX| C
|
||||
N[P2P] -->|Receive TX| C
|
||||
```
|
||||
|
||||
```go reference
|
||||
https://github.com/cosmos/cosmos-sdk/blob/31c604762a434c7b676b6a89897ecbd7c4653a23/baseapp/abci.go#L350-L390
|
||||
```
|
||||
|
||||
## CheckTx Handler
|
||||
|
||||
`CheckTxHandler` allows users to extend the logic of `CheckTx`. `CheckTxHandler` is called by passing context and the transaction bytes received through ABCI. It is required that the handler returns deterministic results given the same transaction bytes.
|
||||
|
||||
:::note
|
||||
we return the raw decoded transaction here to avoid decoding it twice.
|
||||
:::
|
||||
|
||||
```go
|
||||
type CheckTxHandler func(ctx sdk.Context, tx []byte) (Tx, error)
|
||||
```
|
||||
|
||||
Setting a custom `CheckTxHandler` is optional. It can be done from your app.go file:
|
||||
|
||||
```go
|
||||
func NewSimApp(
|
||||
logger log.Logger,
|
||||
db corestore.KVStoreWithBatch,
|
||||
traceStore io.Writer,
|
||||
loadLatest bool,
|
||||
appOpts servertypes.AppOptions,
|
||||
baseAppOptions ...func(*baseapp.BaseApp),
|
||||
) *SimApp {
|
||||
...
|
||||
// Create ChecktxHandler
|
||||
checktxHandler := abci.NewCustomCheckTxHandler(...)
|
||||
app.SetCheckTxHandler(checktxHandler)
|
||||
...
|
||||
}
|
||||
```
|
||||
@ -23,6 +23,11 @@ type ProcessProposalHandler func(Context, *abci.RequestProcessProposal) (*abci.R
|
||||
// PrepareProposalHandler defines a function type alias for preparing a proposal
|
||||
type PrepareProposalHandler func(Context, *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error)
|
||||
|
||||
// CheckTxHandler defines a function type alias for executing logic before transactions are executed.
|
||||
// `RunTx` is a function type alias for executing logic before transactions are executed.
|
||||
// The passed in runtx does not override antehandlers, the execution mode is not passed into runtx to avoid overriding the execution mode.
|
||||
type CheckTxHandler func(RunTx, *abci.RequestCheckTx) (*abci.ResponseCheckTx, error)
|
||||
|
||||
// ExtendVoteHandler defines a function type alias for extending a pre-commit vote.
|
||||
type ExtendVoteHandler func(Context, *abci.RequestExtendVote) (*abci.ResponseExtendVote, error)
|
||||
|
||||
@ -74,3 +79,5 @@ type ResponsePreBlock struct {
|
||||
func (r ResponsePreBlock) IsConsensusParamsChanged() bool {
|
||||
return r.ConsensusParamsChanged
|
||||
}
|
||||
|
||||
type RunTx = func(txBytes []byte, tx Tx) (gInfo GasInfo, result *Result, anteEvents []abci.Event, err error)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user