docs: update docs for Check/DeliverTx middlewares (#11918)
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
co-authored by
Aleksandr Bezobchuk
parent
b0edb6d3ef
commit
9d8beff497
@@ -131,16 +131,16 @@ For user interactions, `PubKey` is formatted using Protobufs JSON ([ProtoMarshal
|
||||
|
||||
A `Keyring` is an object that stores and manages accounts. In the Cosmos SDK, a `Keyring` implementation follows the `Keyring` interface:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.42.1/crypto/keyring/keyring.go#L51-L89
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/crypto/keyring/keyring.go#L54-L101
|
||||
|
||||
The default implementation of `Keyring` comes from the third-party [`99designs/keyring`](https://github.com/99designs/keyring) library.
|
||||
|
||||
A few notes on the `Keyring` methods:
|
||||
|
||||
* `Sign(uid string, payload []byte) ([]byte, sdkcrypto.PubKey, error)` strictly deals with the signature of the `payload` bytes. You must prepare and encode the transaction into a canonical `[]byte` form. Because protobuf is not deterministic, it has been decided in [ADR-020](../architecture/adr-020-protobuf-transaction-encoding.md) that the canonical `payload` to sign is the `SignDoc` struct, deterministically encoded using [ADR-027](../architecture/adr-027-deterministic-protobuf-serialization.md). Note that signature verification is not implemented in the Cosmos SDK by default, it is deferred to the [`anteHandler`](../core/baseapp.md#antehandler).
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.42.1/proto/cosmos/tx/v1beta1/tx.proto#L47-L64
|
||||
* `Sign(uid string, msg []byte) ([]byte, types.PubKey, error)` strictly deals with the signature of the `msg` bytes. You must prepare and encode the transaction into a canonical `[]byte` form. Because protobuf is not deterministic, it has been decided in [ADR-020](../architecture/adr-020-protobuf-transaction-encoding.md) that the canonical `payload` to sign is the `SignDoc` struct, deterministically encoded using [ADR-027](../architecture/adr-027-deterministic-protobuf-serialization.md). Note that signature verification is not implemented in the Cosmos SDK by default, it is deferred to the [`SigVerificationMiddleware middleware`](../core/baseapp.md#middleware).
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/proto/cosmos/tx/v1beta1/tx.proto#L49-L64
|
||||
|
||||
* `NewAccount(uid, mnemonic, bip39Passwd, hdPath string, algo SignatureAlgo) (Info, error)` creates a new account based on the [`bip44 path`](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) and persists it on disk. The `PrivKey` is **never stored unencrypted**, instead it is [encrypted with a passphrase](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/crypto/armor.go) before being persisted. In the context of this method, the key type and sequence number refer to the segment of the BIP44 derivation path (for example, `0`, `1`, `2`, ...) that is used to derive a private and a public key from the mnemonic. Using the same mnemonic and derivation path, the same `PrivKey`, `PubKey` and `Address` is generated. The following keys are supported by the keyring:
|
||||
* `NewAccount(uid, mnemonic, bip39Passphrase, hdPath string, algo SignatureAlgo) (*Record, error)` creates a new account based on the [`bip44 path`](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) and persists it on disk. The `PrivKey` is **never stored unencrypted**, instead it is [encrypted with a passphrase](https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/crypto/armor.go) before being persisted. In the context of this method, the key type and sequence number refer to the segment of the BIP44 derivation path (for example, `0`, `1`, `2`, ...) that is used to derive a private and a public key from the mnemonic. Using the same mnemonic and derivation path, the same `PrivKey`, `PubKey` and `Address` is generated. The following keys are supported by the keyring:
|
||||
|
||||
* `secp256k1`
|
||||
* `ed25519`
|
||||
|
||||
@@ -53,13 +53,13 @@ The first thing defined in `app.go` is the `type` of the application. It is gene
|
||||
|
||||
See an example of application type definition from `simapp`, the Cosmos SDK's own app used for demo and testing purposes:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/simapp/app.go#L145-L187
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/simapp/app.go#L151-L195
|
||||
|
||||
### Constructor Function
|
||||
|
||||
This function constructs a new application of the type defined in the section above. It must fulfill the `AppCreator` signature in order to be used in the [`start` command](../core/node.md#start-command) of the application's daemon command.
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/server/types/app.go#L48-L50
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/server/types/app.go#L57-L59
|
||||
|
||||
Here are the main actions performed by this function:
|
||||
|
||||
@@ -73,7 +73,7 @@ Here are the main actions performed by this function:
|
||||
* Set the remainder of application's parameters:
|
||||
* [`InitChainer`](#initchainer): used to initialize the application when it is first started.
|
||||
* [`BeginBlocker`, `EndBlocker`](#beginblocker-and-endlbocker): called at the beginning and the end of every block).
|
||||
* [`anteHandler`](../core/baseapp.md#antehandler): used to handle fees and signature verification.
|
||||
* [`TxHandler`](../core/baseapp.md#middleware): to setup middlewares, f.e. used to handle fees and signature verification.
|
||||
* Mount the stores.
|
||||
* Return the application.
|
||||
|
||||
@@ -81,7 +81,7 @@ Note that this function only creates an instance of the app, while the actual st
|
||||
|
||||
See an example of application constructor from `simapp`:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/simapp/app.go#L198-L441
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/simapp/app.go#L207-L456
|
||||
|
||||
### InitChainer
|
||||
|
||||
@@ -145,8 +145,8 @@ Note that `sdk.Msg`s are bundled in [transactions](../core/transactions.md), and
|
||||
|
||||
When a valid block of transactions is received by the full-node, Tendermint relays each one to the application via [`DeliverTx`](https://docs.tendermint.com/master/spec/abci/apps.html#delivertx). Then, the application handles the transaction:
|
||||
|
||||
1. Upon receiving the transaction, the application first unmarshalls it from `[]bytes`.
|
||||
2. Then, it verifies a few things about the transaction like [fee payment and signatures](#gas-fees.md#antehandler) before extracting the `Msg`(s) contained in the transaction.
|
||||
1. Upon receiving the transaction, the application first unmarshalls it from `[]byte`.
|
||||
2. Then, it verifies a few things about the transaction like [fee payment and signatures](./gas-fees.md#middleware) before extracting the `Msg`(s) contained in the transaction.
|
||||
3. `sdk.Msg`s are encoded using Protobuf [`Any`s](#register-codec). By analyzing each `Any`'s `type_url`, baseapp's `msgServiceRouter` routes the `sdk.Msg` to the corresponding module's `Msg` service.
|
||||
4. If the message is successfully processed, the state is updated.
|
||||
|
||||
|
||||
+11
-17
@@ -15,7 +15,7 @@ This document describes the default strategies to handle gas and fees within a C
|
||||
In the Cosmos SDK, `gas` is a special unit that is used to track the consumption of resources during execution. `gas` is typically consumed whenever read and writes are made to the store, but it can also be consumed if expensive computation needs to be done. It serves two main purposes:
|
||||
|
||||
* Make sure blocks are not consuming too many resources and will be finalized. This is implemented by default in the Cosmos SDK via the [block gas meter](#block-gas-meter).
|
||||
* Prevent spam and abuse from end-user. To this end, `gas` consumed during [`message`](../building-modules/messages-and-queries.md#messages) execution is typically priced, resulting in a `fee` (`fees = gas * gas-prices`). `fees` generally have to be paid by the sender of the `message`. Note that the Cosmos SDK does not enforce `gas` pricing by default, as there may be other ways to prevent spam (e.g. bandwidth schemes). Still, most applications will implement `fee` mechanisms to prevent spam. This is done via the [`AnteHandler`](#antehandler).
|
||||
* Prevent spam and abuse from end-user. To this end, `gas` consumed during [`message`](../building-modules/messages-and-queries.md#messages) execution is typically priced, resulting in a `fee` (`fees = gas * gas-prices`). `fees` generally have to be paid by the sender of the `message`. Note that the Cosmos SDK does not enforce `gas` pricing by default, as there may be other ways to prevent spam (e.g. bandwidth schemes). Still, most applications will implement `fee` mechanisms to prevent spam. This is done via the [`GasTxMiddleware` middleware](#middleware).
|
||||
|
||||
## Gas Meter
|
||||
|
||||
@@ -42,7 +42,7 @@ By default, the Cosmos SDK makes use of two different gas meters, the [main gas
|
||||
|
||||
### Main Gas Meter
|
||||
|
||||
`ctx.GasMeter()` is the main gas meter of the application. The main gas meter is initialized in `BeginBlock` via `setDeliverState`, and then tracks gas consumption during execution sequences that lead to state-transitions, i.e. those originally triggered by [`BeginBlock`](../core/baseapp.md#beginblock), [`DeliverTx`](../core/baseapp.md#delivertx) and [`EndBlock`](../core/baseapp.md#endblock). At the beginning of each `DeliverTx`, the main gas meter **must be set to 0** in the [`AnteHandler`](#antehandler), so that it can track gas consumption per-transaction.
|
||||
`ctx.GasMeter()` is the main gas meter of the application. The main gas meter is initialized in `BeginBlock` via `setDeliverState`, and then tracks gas consumption during execution sequences that lead to state-transitions, i.e. those originally triggered by [`BeginBlock`](../core/baseapp.md#beginblock), [`DeliverTx`](../core/baseapp.md#delivertx) and [`EndBlock`](../core/baseapp.md#endblock). At the beginning of each `DeliverTx`, the main gas meter **must be set to 0** in the [`GasTxMiddleware` middleware](#middleware), so that it can track gas consumption per-transaction.
|
||||
|
||||
Gas consumption can be done manually, generally by the module developer in the [`BeginBlocker`, `EndBlocker`](../building-modules/beginblock-endblock.md) or [`Msg` service](../building-modules/msg-services.md), but most of the time it is done automatically whenever there is a read or write to the store. This automatic gas consumption logic is implemented in a special store called [`GasKv`](../core/store.md#gaskv-store).
|
||||
|
||||
@@ -61,28 +61,22 @@ ctx.BlockGasMeter().ConsumeGas(
|
||||
)
|
||||
```
|
||||
|
||||
## AnteHandler
|
||||
## Middleware
|
||||
|
||||
The `AnteHandler` is run for every transaction during `CheckTx` and `DeliverTx`, before a Protobuf `Msg` service method for each `sdk.Msg` in the transaction. `AnteHandler`s have the following signature:
|
||||
The `GasTxMiddleware` is run for every transaction during `CheckTx` and `DeliverTx`, before a Protobuf `Msg` service method for each `sdk.Msg` in the transaction.
|
||||
|
||||
```go
|
||||
// AnteHandler authenticates transactions, before their internal messages are handled.
|
||||
// If newCtx.IsZero(), ctx is used instead.
|
||||
type AnteHandler func(ctx Context, tx Tx, simulate bool) (newCtx Context, result Result, abort bool)
|
||||
```
|
||||
The `GasTxMiddleware` is not implemented in the core Cosmos SDK but in a module. That said, most applications today use the default implementation defined in the [`auth` module](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth/middleware). Here is what the `middleware` is intended to do in a normal Cosmos SDK application:
|
||||
|
||||
The `anteHandler` is not implemented in the core Cosmos SDK but in a module. This gives the possibility to developers to choose which version of `AnteHandler` fits their application's needs. That said, most applications today use the default implementation defined in the [`auth` module](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth). Here is what the `anteHandler` is intended to do in a normal Cosmos SDK application:
|
||||
|
||||
* Verify that the transaction are of the correct type. Transaction types are defined in the module that implements the `anteHandler`, and they follow the transaction interface:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/types/tx_msg.go#L49-L57
|
||||
* Verify that the transaction are of the correct type. Transaction types are defined in the module that implements the `middleware`, and they follow the transaction interface:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/types/tx_msg.go#L38-L46
|
||||
This enables developers to play with various types for the transaction of their application. In the default `auth` module, the default transaction type is `Tx`:
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/proto/cosmos/tx/v1beta1/tx.proto#L12-L25
|
||||
* Verify signatures for each [`message`](../building-modules/messages-and-queries.md#messages) contained in the transaction. Each `message` should be signed by one or multiple sender(s), and these signatures must be verified in the `anteHandler`.
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-beta2/proto/cosmos/tx/v1beta1/tx.proto#L13-L26
|
||||
* Verify signatures for each [`message`](../building-modules/messages-and-queries.md#messages) contained in the transaction. Each `message` should be signed by one or multiple sender(s), and these signatures must be verified by a `middleware`.
|
||||
* During `CheckTx`, verify that the gas prices provided with the transaction is greater than the local `min-gas-prices` (as a reminder, gas-prices can be deducted from the following equation: `fees = gas * gas-prices`). `min-gas-prices` is a parameter local to each full-node and used during `CheckTx` to discard transactions that do not provide a minimum amount of fees. This ensure that the mempool cannot be spammed with garbage transactions.
|
||||
* Verify that the sender of the transaction has enough funds to cover for the `fees`. When the end-user generates a transaction, they must indicate 2 of the 3 following parameters (the third one being implicit): `fees`, `gas` and `gas-prices`. This signals how much they are willing to pay for nodes to execute their transaction. The provided `gas` value is stored in a parameter called `GasWanted` for later use.
|
||||
* Set `newCtx.GasMeter` to 0, with a limit of `GasWanted`. **This step is extremely important**, as it not only makes sure the transaction cannot consume infinite gas, but also that `ctx.GasMeter` is reset in-between each `DeliverTx` (`ctx` is set to `newCtx` after `anteHandler` is run, and the `anteHandler` is run each time `DeliverTx` is called).
|
||||
* Set `newCtx.GasMeter` to 0, with a limit of `GasWanted`. **This step is extremely important**, as it not only makes sure the transaction cannot consume infinite gas, but also that `ctx.GasMeter` is reset in-between each `DeliverTx` (`ctx.GasMeter` is set by the middleware each time `DeliverTx` is called).
|
||||
|
||||
As explained above, the `anteHandler` returns a maximum limit of `gas` the transaction can consume during execution called `GasWanted`. The actual amount consumed in the end is denominated `GasUsed`, and we must therefore have `GasUsed =< GasWanted`. Both `GasWanted` and `GasUsed` are relayed to the underlying consensus engine when [`DeliverTx`](../core/baseapp.md#delivertx) returns.
|
||||
As explained above, the middleware returns a maximum limit of `gas` the transaction can consume during execution called `GasWanted`. The actual amount consumed in the end is denominated `GasUsed`, and we must therefore have `GasUsed =< GasWanted`. Both `GasWanted` and `GasUsed` are relayed to the underlying consensus engine when [`DeliverTx`](../core/baseapp.md#delivertx) returns.
|
||||
|
||||
## Next {hide}
|
||||
|
||||
|
||||
+12
-12
@@ -68,7 +68,7 @@ are not empty, enforcing nonnegative numbers, and other logic specified in the d
|
||||
**_Stateful_** checks validate transactions and messages based on a committed state. Examples
|
||||
include checking that the relevant values exist and are able to be transacted with, the address
|
||||
has sufficient funds, and the sender is authorized or has the correct ownership to transact.
|
||||
At any given moment, full-nodes typically have [multiple versions](../core/baseapp.md#volatile-states)
|
||||
At any given moment, full-nodes typically have [multiple versions](../core/baseapp.md#state-updates)
|
||||
of the application's internal state for different purposes. For example, nodes will execute state
|
||||
changes while in the process of verifying transactions, but still need a copy of the last committed
|
||||
state in order to answer queries - they should not respond using state with uncommitted changes.
|
||||
@@ -79,7 +79,7 @@ through several steps, beginning with decoding `Tx`.
|
||||
|
||||
### Decoding
|
||||
|
||||
When `Tx` is received by the application from the underlying consensus engine (e.g. Tendermint), it is still in its [encoded](../core/encoding.md) `[]byte` form and needs to be unmarshaled in order to be processed. Then, the [`runTx`](../core/baseapp.md#runtx-and-runmsgs) function is called to run in `runTxModeCheck` mode, meaning the function will run all checks but exit before executing messages and writing state changes.
|
||||
When `Tx` is received by the application from the underlying consensus engine (e.g. Tendermint), it is still in its [encoded](../core/encoding.md) `[]byte` form and needs to be unmarshaled in order to be processed. Then, the transaction is passed to the middlewares defined in `tx.Handler`. The middlewares will performe additional checks defined in their own `CheckTx`, meaning the they will run all checks but exit before executing messages and writing state changes.
|
||||
|
||||
### ValidateBasic
|
||||
|
||||
@@ -96,13 +96,13 @@ Example, if the message is to send coins from one address to another, `ValidateB
|
||||
|
||||
See also [Msg Service Validation](../building-modules/msg-services.md#Validation).
|
||||
|
||||
### AnteHandler
|
||||
### Middlewares
|
||||
|
||||
After the ValidateBasic checks, the `AnteHandler`s are run. Technically, they are optional, but in practice, they are very often present to perform signature verification, gas calculation, fee deduction and other core operations related to blockchain transactions.
|
||||
Middlewares implements the `TxHandler` interface and are defined by `tx.Handler` in `BaseApp`. This architecture allows to run the middlewares logic in the `CheckTx` and `DeliverTx` phases. Technically, they are optional, but in practice, they are very often present to perform signature verification, gas calculation, fee deduction and other core operations related to blockchain transactions.
|
||||
|
||||
A copy of the cached context is provided to the `AnteHandler`, which performs limited checks specified for the transaction type. Using a copy allows the AnteHandler to do stateful checks for `Tx` without modifying the last committed state, and revert back to the original if the execution fails.
|
||||
A copy of the cached context is provided to the middleware, which performs limited checks specified for the transaction type. Using a copy allows the Middleware to do stateful checks for `Tx` without modifying the last committed state, and revert back to the original if the execution fails.
|
||||
|
||||
For example, the [`auth`](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth/spec) module `AnteHandler` checks and increments sequence numbers, checks signatures and account numbers, and deducts fees from the first signer of the transaction - all state changes are made using the `checkState`.
|
||||
For example, the [`auth`](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth/spec) middlewares can check and increment sequence numbers, check signatures and account numbers, and deduct fees from the first signer of the transaction - all state changes are made using the `checkState`.
|
||||
|
||||
### Gas
|
||||
|
||||
@@ -201,19 +201,19 @@ Instead of using their `checkState`, full-nodes use `deliverState`:
|
||||
* **Decoding:** Since `DeliverTx` is an ABCI call, `Tx` is received in the encoded `[]byte` form.
|
||||
Nodes first unmarshal the transaction, using the [`TxConfig`](./app-anatomy#register-codec) defined in the app, then call `runTx` in `runTxModeDeliver`, which is very similar to `CheckTx` but also executes and writes state changes.
|
||||
|
||||
* **Checks:** Full-nodes call `validateBasicMsgs` and the `AnteHandler` again. This second check
|
||||
* **Checks:** Full-nodes call `validateBasicMsgs` and the `DeliverTx` from the middleware again. This second check
|
||||
happens because they may not have seen the same transactions during the addition to Mempool stage\
|
||||
and a malicious proposer may have included invalid ones. One difference here is that the
|
||||
`AnteHandler` will not compare `gas-prices` to the node's `min-gas-prices` since that value is local
|
||||
middleware will not compare `gas-prices` to the node's `min-gas-prices` since that value is local
|
||||
to each node - differing values across nodes would yield nondeterministic results.
|
||||
|
||||
* **`MsgServiceRouter`:** While `CheckTx` would have exited, `DeliverTx` continues to run
|
||||
[`runMsgs`](../core/baseapp.md#runtx-and-runmsgs) to fully execute each `Msg` within the transaction.
|
||||
* **`RunMsgsTxHandler`:** While `CheckTx` would have exited, `DeliverTx` continues to run
|
||||
[`RunMsgsTxHandler`](../core/baseapp.md#middlewares) to fully execute each `Msg` within the transaction.
|
||||
Since the transaction may have messages from different modules, `BaseApp` needs to know which module
|
||||
to find the appropriate handler. This is achieved using `BaseApp`'s `MsgServiceRouter` so that it can be processed by the module's Protobuf [`Msg` service](../building-modules/msg-services.md).
|
||||
to find the appropriate handler. This is achieved using `RunMsgsTxHandler`'s `MsgServiceRouter` so that it can be processed by the module's Protobuf [`Msg` service](../building-modules/msg-services.md).
|
||||
For `LegacyMsg` routing, the `Route` function is called via the [module manager](../building-modules/module-manager.md) to retrieve the route name and find the legacy [`Handler`](../building-modules/msg-services.md#handler-type) within the module.
|
||||
|
||||
* **`Msg` service:** a Protobuf `Msg` service, a step up from `AnteHandler`, is responsible for executing each
|
||||
* **`Msg` service:** a Protobuf `Msg` service is responsible for executing each
|
||||
message in the `Tx` and causes state transitions to persist in `deliverTxState`.
|
||||
|
||||
* **Gas:** While a `Tx` is being delivered, a `GasMeter` is used to keep track of how much
|
||||
|
||||
Reference in New Issue
Block a user