Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RecheckTx Optimizations #5196

Merged
merged 23 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
420faef
Introduce Recheck optimizations
AdityaSripal Oct 15, 2019
dc98bb2
Merge branch 'master' into aditya/recheck
alexanderbez Oct 15, 2019
0310b36
fix tests
AdityaSripal Oct 15, 2019
42ddba9
Merge branch 'aditya/recheck' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Oct 15, 2019
17b47bf
add unit tests
AdityaSripal Oct 15, 2019
4eb7dc8
add integration tests
AdityaSripal Oct 15, 2019
48bc5cb
update changelog
AdityaSripal Oct 15, 2019
af3b24d
Update x/auth/ante/ante_test.go
AdityaSripal Oct 15, 2019
93fd162
Merge branch 'master' into aditya/recheck
fedekunze Oct 16, 2019
2e24c37
Merge branch 'master' into aditya/recheck
alexanderbez Oct 22, 2019
32f087b
enforce invariant, that recheck mode has IsCheckTx() = true
AdityaSripal Oct 22, 2019
3f94b1c
Merge branch 'aditya/recheck' of https://github.com/cosmos/cosmos-sdk…
AdityaSripal Oct 22, 2019
14f1939
address reviews
AdityaSripal Oct 22, 2019
81a5996
update changelog
alexanderbez Oct 22, 2019
b5232ee
lint: update godoc
alexanderbez Oct 22, 2019
8d2e54b
docs: update checktx section in baseapp doc
alexanderbez Oct 22, 2019
27d0393
doc: update IncrementSequenceDecorator godoc
alexanderbez Oct 22, 2019
08a44d6
cleanup iota const
alexanderbez Oct 23, 2019
39b91bf
remove named return
alexanderbez Oct 23, 2019
0af80d5
Update docs/core/baseapp.md
alexanderbez Oct 23, 2019
a0f895f
Update docs/core/baseapp.md
alexanderbez Oct 23, 2019
da856be
Merge branch 'master' into aditya/recheck
alexanderbez Oct 23, 2019
d14f4ee
Merge branch 'master' into aditya/recheck
alexanderbez Oct 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ correct version via: `pkgutil --pkg-info=com.apple.pkg.CLTools_Executables`.
to the new keyring.
* [\#4486](https://github.com/cosmos/cosmos-sdk/issues/4486) Introduce new `PeriodicVestingAccount` vesting account type
that allows for arbitrary vesting periods.
* (baseapp) [\#5196](https://github.com/cosmos/cosmos-sdk/pull/5196) Baseapp has a new `runTxModeReCheck` to allow applications to skip expensive and unnecessary re-checking of transactions.
* (types) [\#5196](https://github.com/cosmos/cosmos-sdk/pull/5196) Context has new `IsRecheckTx() bool` and `WithIsReCheckTx(bool) Context` methods to to be used in the `AnteHandler`.
* (x/auth/ante) [\#5196](https://github.com/cosmos/cosmos-sdk/pull/5196) AnteDecorators have been updated to avoid unnecessary checks when `ctx.IsReCheckTx() == true`
* (x/auth) [\#5006](https://github.com/cosmos/cosmos-sdk/pull/5006) Modular `AnteHandler` via composable decorators:
* The `AnteDecorator` interface has been introduced to allow users to implement modular `AnteHandler`
functionality that can be composed together to create a single `AnteHandler` rather than implementing
Expand Down
9 changes: 7 additions & 2 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,15 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckTx)
var result sdk.Result

tx, err := app.txDecoder(req.Tx)
if err != nil {
switch {
case err != nil:
result = err.Result()
} else {
case req.Type == abci.CheckTxType_New:
result = app.runTx(runTxModeCheck, req.Tx, tx)
case req.Type == abci.CheckTxType_Recheck:
result = app.runTx(runTxModeReCheck, req.Tx, tx)
default:
panic(fmt.Sprintf("Unknown RequestCheckTx Type: %v", req.Type))
}

return abci.ResponseCheckTx{
Expand Down
29 changes: 15 additions & 14 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ import (
)

const (
// Check a transaction
runTxModeCheck runTxMode = iota
// Simulate a transaction
runTxModeSimulate runTxMode = iota
// Deliver a transaction
runTxModeDeliver runTxMode = iota
runTxModeCheck runTxMode = iota // Check a transaction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
runTxModeCheck runTxMode = iota // Check a transaction
runTxModeCheck runTxMode = iota + 1 // Check a transaction

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? This is ineffectual.

runTxModeReCheck // Recheck a (pending) transaction after a commit
runTxModeSimulate // Simulate a transaction
runTxModeDeliver // Deliver a transaction

// MainStoreKey is the string representation of the main store
MainStoreKey = "main"
Expand Down Expand Up @@ -467,25 +465,28 @@ func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error {
// Returns the applications's deliverState if app is in runTxModeDeliver,
// otherwise it returns the application's checkstate.
func (app *BaseApp) getState(mode runTxMode) *state {
if mode == runTxModeCheck || mode == runTxModeSimulate {
return app.checkState
if mode == runTxModeDeliver {
return app.deliverState
}

return app.deliverState
return app.checkState
}

// retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Context) {
ctx = app.getState(mode).ctx.
func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context {
ctx := app.getState(mode).ctx.
WithTxBytes(txBytes).
WithVoteInfos(app.voteInfos).
WithConsensusParams(app.consensusParams)

AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
if mode == runTxModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
}
if mode == runTxModeSimulate {
ctx, _ = ctx.CacheContext()
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
}

return
return ctx
}

// cacheTxContext returns a new context based off of the provided context with
Expand Down Expand Up @@ -653,8 +654,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re

var msgResult sdk.Result

// skip actual execution for CheckTx mode
if mode != runTxModeCheck {
// skip actual execution for CheckTx and ReCheckTx mode
if mode != runTxModeCheck && mode != runTxModeReCheck {
msgResult = handler(ctx, msg)
}

Expand Down
61 changes: 48 additions & 13 deletions docs/core/baseapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ functionalities of an SDK application.
- [Query Routing](#query-routing)
- [Main ABCI Messages](#main-abci-messages)
- [CheckTx](#checktx-1)
- [RecheckTx](#rechecktx)
- [DeliverTx](#delivertx-1)
- [RunTx, AnteHandler and RunMsgs](#runtx-antehandler-and-runmsgs)
- [RunTx](#runtx)
Expand Down Expand Up @@ -103,7 +104,7 @@ raw transaction bytes relayed by the underlying Tendermint engine.
used to persist data related to the core of the application, like consensus parameters.
- [`AnteHandler`](#antehandler): This handler is used to handle signature verification, fee payment,
and other pre-message execution checks when a transaction is received. It's executed during
[`CheckTx`](#checktx-1) and [`DeliverTx`](#delivertx-1).
[`CheckTx/RecheckTx`](#checktx-1) and [`DeliverTx`](#delivertx-1).
- [`InitChainer`](../basics/app-anatomy.md#initchainer),
[`BeginBlocker` and `EndBlocker`](../basics/app-anatomy.md#beginblocker-and-endblocker): These are
the functions executed when the application receives the `InitChain`, `BeginBlock` and `EndBlock`
Expand Down Expand Up @@ -240,28 +241,62 @@ Developers building on top of the Cosmos SDK need not implement the ABCI themsel

### CheckTx

`CheckTx` is sent by the underlying consensus engine when a new unconfirmed (i.e. not yet included in a valid block) transaction is received by a full-node. The role of `CheckTx` is to guard the full-node's mempool (where unconfirmed transactions are stored until they are included in a block) from spam transactions. Unconfirmed transactions are relayed to peers only if they pass `CheckTx`.
`CheckTx` is sent by the underlying consensus engine when a new unconfirmed (i.e. not yet included in a valid block)
transaction is received by a full-node. The role of `CheckTx` is to guard the full-node's mempool
(where unconfirmed transactions are stored until they are included in a block) from spam transactions.
Unconfirmed transactions are relayed to peers only if they pass `CheckTx`.

`CheckTx()` can perform both _stateful_ and _stateless_ checks, but developers should strive to make them lightweight. In the Cosmos SDK, after decoding transactions, `CheckTx()` is implemented to do the following checks:
`CheckTx()` can perform both _stateful_ and _stateless_ checks, but developers should strive to
make them lightweight. In the Cosmos SDK, after decoding transactions, `CheckTx()` is implemented
to do the following checks:

1. Extract the `message`s from the transaction.
2. Perform _stateless_ checks by calling `ValidateBasic()` on each of the `messages`. This is done first, as _stateless_ checks are less computationally expensive than _stateful_ checks. If `ValidateBasic()` fail, `CheckTx` returns before running _stateful_ checks, which saves resources.
3. Perform non-module related _stateful_ checks on the account. This step is mainly about checking that the `message` signatures are valid, that enough fees are provided and that the sending account has enough funds to pay for said fees. Note that no precise `gas` counting occurs here, as `message`s are not processed. Usually, the `anteHandler` will check that the `gas` provided with the transaction is superior to a minimum reference gas amount based on the raw transaction size, in order to avoid spam with transactions that provide 0 gas.
4. Ensure that a [`Route`](#message-routing) exists for each `message`, but do **not** actually process `message`s. `Message`s only need to be processed when the canonical state need to be updated, which happens during `DeliverTx`.

Steps 2. and 3. are performed by the `anteHandler` in the [`RunTx()`](<#runtx()-,antehandler-and-runmsgs()>) function, which `CheckTx()` calls with the `runTxModeCheck` mode. During each step of `CheckTx()`, a special [volatile state](#volatile-states) called `checkState` is updated. This state is used to keep track of the temporary changes triggered by the `CheckTx()` calls of each transaction without modifying the [main canonical state](#main-state) . For example, when a transaction goes through `CheckTx()`, the transaction's fees are deducted from the sender's account in `checkState`. If a second transaction is received from the same account before the first is processed, and the account has consumed all its funds in `checkState` during the first transaction, the second transaction will fail `CheckTx`() and be rejected. In any case, the sender's account will not actually pay the fees until the transaction is actually included in a block, because `checkState` never gets committed to the main state. `checkState` is reset to the latest state of the main state each time a blocks gets [committed](#commit).

`CheckTx` returns a response to the underlying consensus engine of type [`abci.ResponseCheckTx`](https://tendermint.com/docs/spec/abci/abci.html#messages). The response contains:
2. Perform _stateless_ checks by calling `ValidateBasic()` on each of the `messages`. This is done
first, as _stateless_ checks are less computationally expensive than _stateful_ checks. If
`ValidateBasic()` fail, `CheckTx` returns before running _stateful_ checks, which saves resources.
3. Perform non-module related _stateful_ checks on the account. This step is mainly about checking
that the `message` signatures are valid, that enough fees are provided and that the sending account
has enough funds to pay for said fees. Note that no precise `gas` counting occurs here,
as `message`s are not processed. Usually, the `AnteHandler` will check that the `gas` provided
with the transaction is superior to a minimum reference gas amount based on the raw transaction size,
in order to avoid spam with transactions that provide 0 gas.
4. Ensure that a [`Route`](#message-routing) exists for each `message`, but do **not** actually
process `message`s. `Message`s only need to be processed when the canonical state need to be updated,
which happens during `DeliverTx`.

Steps 2. and 3. are performed by the `AnteHandler` in the [`RunTx()`](<#runtx()-,antehandler-and-runmsgs()>)
function, which `CheckTx()` calls with the `runTxModeCheck` mode. During each step of `CheckTx()`, a
special [volatile state](#volatile-states) called `checkState` is updated. This state is used to keep
track of the temporary changes triggered by the `CheckTx()` calls of each transaction without modifying
the [main canonical state](#main-state) . For example, when a transaction goes through `CheckTx()`, the
transaction's fees are deducted from the sender's account in `checkState`. If a second transaction is
received from the same account before the first is processed, and the account has consumed all its
funds in `checkState` during the first transaction, the second transaction will fail `CheckTx`() and
be rejected. In any case, the sender's account will not actually pay the fees until the transaction
is actually included in a block, because `checkState` never gets committed to the main state. The
`checkState` is reset to the latest state of the main state each time a blocks gets [committed](#commit).

`CheckTx` returns a response to the underlying consensus engine of type [`abci.ResponseCheckTx`](https://tendermint.com/docs/spec/abci/abci.html#messages).
The response contains:

- `Code (uint32)`: Response Code. `0` if successful.
- `Data ([]byte)`: Result bytes, if any.
- `Log (string):` The output of the application's logger. May be non-deterministic.
- `Info (string):` Additional information. May be non-deterministic.
- `GasWanted (int64)`: Amount of gas requested for transaction. It is provided by users when they generate the transaction.
- `GasUsed (int64)`: Amount of gas consumed by transaction. During `CheckTx`, this value is computed by multiplying the standard cost of a transaction byte by the size of the raw transaction (click [here](https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/ante.go#L101) for an example).
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing transactions (eg. by account).
- `Events ([]Event)`: Key-Value events for filtering and indexing transactions (eg. by account or message type).
- `Codespace (string)`: Namespace for the Code.

#### RecheckTx

After `Commit`, `CheckTx` is run again on all transactions that remain in the node's local mempool
after filtering those included in the block. To prevent the mempool from rechecking all transactions
every time a block is committed, the configuration option `mempool.recheck=false` can be set. As of
Tendermint v0.32.1, an additional `Type` parameter is made available to the `CheckTx` function that
indicates whether an incoming transaction is new (`CheckTxType_New`), or a recheck (`CheckTxType_Recheck`).
This allows certain checks like signature verification can be skipped during `CheckTxType_Recheck`.

### DeliverTx

When the underlying consensus engine receives a block proposal, each transaction in the block needs to be processed by the application. To that end, the underlying consensus engine sends a `DeliverTx` message to the application for each transaction in a sequential order.
Expand All @@ -270,8 +305,8 @@ Before the first transaction of a given block is processed, a [volatile state](#

`DeliverTx` performs the **exact same steps as `CheckTx`**, with a little caveat at step 3 and the addition of a fifth step:

3. The `anteHandler` does **not** check that the transaction's `gas-prices` is sufficient. That is because the `min-gas-prices` value `gas-prices` is checked against is local to the node, and therefore what is enough for one full-node might not be for another. This means that the proposer can potentially include transactions for free, although they are not incentivised to do so, as they earn a bonus on the total fee of the block they propose.
4. For each `message` in the transaction, route to the appropriate module's `handler`. Additional _stateful_ checks are performed, and the cache-wrapped multistore held in `deliverState`'s `context` is updated by the module's `keeper`. If the `handler` returns successfully, the cache-wrapped multistore held in `context` is written to `deliverState` `CacheMultiStore`.
1. The `AnteHandler` does **not** check that the transaction's `gas-prices` is sufficient. That is because the `min-gas-prices` value `gas-prices` is checked against is local to the node, and therefore what is enough for one full-node might not be for another. This means that the proposer can potentially include transactions for free, although they are not incentivised to do so, as they earn a bonus on the total fee of the block they propose.
2. For each `message` in the transaction, route to the appropriate module's `handler`. Additional _stateful_ checks are performed, and the cache-wrapped multistore held in `deliverState`'s `context` is updated by the module's `keeper`. If the `handler` returns successfully, the cache-wrapped multistore held in `context` is written to `deliverState` `CacheMultiStore`.

During step 5., each read/write to the store increases the value of `GasConsumed`. You can find the default cost of each operation [here](https://github.com/cosmos/cosmos-sdk/blob/master/store/types/gas.go#L142-L150). At any point, if `GasConsumed > GasWanted`, the function returns with `Code != 0` and `DeliverTx()` fails.

Expand Down
12 changes: 12 additions & 0 deletions types/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Context struct {
gasMeter GasMeter
blockGasMeter GasMeter
checkTx bool
recheckTx bool // if recheckTx == true, then checkTx must also be true
minGasPrice DecCoins
consParams *abci.ConsensusParams
eventManager *EventManager
Expand All @@ -51,6 +52,7 @@ func (c Context) VoteInfos() []abci.VoteInfo { return c.voteInfo }
func (c Context) GasMeter() GasMeter { return c.gasMeter }
func (c Context) BlockGasMeter() GasMeter { return c.blockGasMeter }
func (c Context) IsCheckTx() bool { return c.checkTx }
func (c Context) IsReCheckTx() bool { return c.recheckTx }
func (c Context) MinGasPrices() DecCoins { return c.minGasPrice }
func (c Context) EventManager() *EventManager { return c.eventManager }

Expand Down Expand Up @@ -152,6 +154,16 @@ func (c Context) WithIsCheckTx(isCheckTx bool) Context {
return c
}

// WithIsRecheckTx called with true will also set true on checkTx in order to
// enforce the invariant that if recheckTx = true then checkTx = true as well.
func (c Context) WithIsReCheckTx(isRecheckTx bool) Context {
if isRecheckTx {
c.checkTx = true
}
c.recheckTx = isRecheckTx
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
return c
}

func (c Context) WithMinGasPrices(gasPrices DecCoins) Context {
c.minGasPrice = gasPrices
return c
Expand Down
Loading