docs: cache-wrapping and virtual store (#8102)
* docs: cache-wrapping and virtual store * Finish the docs update * Update docs/building-modules/msg-services.md Co-authored-by: Amaury <amaury.martiny@protonmail.com> * Update docs/building-modules/msg-services.md Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: Amaury <amaury.martiny@protonmail.com> Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
parent
46b697ca23
commit
e481f13ff3
@ -286,7 +286,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||
header := app.deliverState.ctx.BlockHeader()
|
||||
retainHeight := app.GetBlockRetentionHeight(header.Height)
|
||||
|
||||
// Write the DeliverTx state which is cache-wrapped and commit the MultiStore.
|
||||
// Write the DeliverTx state into branched storage and commit the MultiStore.
|
||||
// The write to the DeliverTx state writes all state transitions to the root
|
||||
// MultiStore (app.cms) so when Commit() is called is persists those values.
|
||||
app.deliverState.ms.Write()
|
||||
@ -629,7 +629,7 @@ func (app *BaseApp) createQueryContext(height int64, prove bool) (sdk.Context, e
|
||||
)
|
||||
}
|
||||
|
||||
// cache wrap the commit-multistore for safety
|
||||
// branch the commit-multistore for safety
|
||||
ctx := sdk.NewContext(
|
||||
cacheMS, app.checkState.ctx.BlockHeader(), true, app.logger,
|
||||
).WithMinGasPrices(app.minGasPrices)
|
||||
|
||||
@ -357,8 +357,8 @@ func (app *BaseApp) Seal() { app.sealed = true }
|
||||
// IsSealed returns true if the BaseApp is sealed and false otherwise.
|
||||
func (app *BaseApp) IsSealed() bool { return app.sealed }
|
||||
|
||||
// setCheckState sets the BaseApp's checkState with a cache-wrapped multi-store
|
||||
// (i.e. a CacheMultiStore) and a new Context with the cache-wrapped multi-store,
|
||||
// setCheckState sets the BaseApp's checkState with a branched multi-store
|
||||
// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch,
|
||||
// provided header, and minimum gas prices set. It is set on InitChain and reset
|
||||
// on Commit.
|
||||
func (app *BaseApp) setCheckState(header tmproto.Header) {
|
||||
@ -369,8 +369,8 @@ func (app *BaseApp) setCheckState(header tmproto.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// setDeliverState sets the BaseApp's deliverState with a cache-wrapped multi-store
|
||||
// (i.e. a CacheMultiStore) and a new Context with the cache-wrapped multi-store,
|
||||
// setDeliverState sets the BaseApp's deliverState with a branched multi-store
|
||||
// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch,
|
||||
// and provided header. It is set on InitChain and BeginBlock and set to nil on
|
||||
// Commit.
|
||||
func (app *BaseApp) setDeliverState(header tmproto.Header) {
|
||||
@ -532,7 +532,7 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context
|
||||
}
|
||||
|
||||
// cacheTxContext returns a new context based off of the provided context with
|
||||
// a cache wrapped multi-store.
|
||||
// a branched multi-store.
|
||||
func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, sdk.CacheMultiStore) {
|
||||
ms := ctx.MultiStore()
|
||||
// TODO: https://github.com/cosmos/cosmos-sdk/issues/2824
|
||||
@ -620,7 +620,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
|
||||
msCache sdk.CacheMultiStore
|
||||
)
|
||||
|
||||
// Cache wrap context before AnteHandler call in case it aborts.
|
||||
// Branch context before AnteHandler call in case it aborts.
|
||||
// This is required for both CheckTx and DeliverTx.
|
||||
// Ref: https://github.com/cosmos/cosmos-sdk/issues/2772
|
||||
//
|
||||
@ -632,9 +632,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
|
||||
newCtx, err := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate)
|
||||
|
||||
if !newCtx.IsZero() {
|
||||
// At this point, newCtx.MultiStore() is cache-wrapped, or something else
|
||||
// replaced by the AnteHandler. We want the original multistore, not one
|
||||
// which was cache-wrapped for the AnteHandler.
|
||||
// At this point, newCtx.MultiStore() is a store branch, or something else
|
||||
// replaced by the AnteHandler. We want the original multistore.
|
||||
//
|
||||
// Also, in the case of the tx aborting, we need to track gas consumed via
|
||||
// the instantiated gas meter in the AnteHandler, so we update the context
|
||||
@ -654,9 +653,9 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
|
||||
msCache.Write()
|
||||
}
|
||||
|
||||
// Create a new Context based off of the existing Context with a cache-wrapped
|
||||
// MultiStore in case message processing fails. At this point, the MultiStore
|
||||
// is doubly cached-wrapped.
|
||||
// Create a new Context based off of the existing Context with a MultiStore branch
|
||||
// in case message processing fails. At this point, the MultiStore
|
||||
// is a branch of a branch.
|
||||
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
|
||||
|
||||
// Attempt to execute all messages and only update state if all messages pass
|
||||
|
||||
@ -67,7 +67,7 @@ Here is the typical structure of a `handler` function:
|
||||
Let us break it down:
|
||||
|
||||
- The [`Msg`](./messages-and-queries.md#messages) is the actual object being processed.
|
||||
- The [`Context`](../core/context.md) contains all the necessary information needed to process the `msg`, as well as a cache-wrapped copy of the latest state. If the `msg` is succesfully processed, the modified version of the temporary state contained in the `ctx` will be written to the main state.
|
||||
- The [`Context`](../core/context.md) contains all the necessary information needed to process the `msg`, as well as a branch of the latest state. If the `msg` is successfully processed, the branched version of the state contained in the `ctx` will be written to the main state (branch).
|
||||
- The [`*Result`] returned to `BaseApp` contains (among other things) information on the execution of the `handler` and [events](../core/events.md).
|
||||
|
||||
Module `handler`s are typically implemented in a `./handler.go` file inside the module's folder. The [module manager](./module-manager.md) is used to add the module's `handler`s to the
|
||||
|
||||
@ -21,7 +21,7 @@ Let us break it down:
|
||||
|
||||
- The `path` is an array of `string`s that contains the type of the query, and that can also contain `query` arguments. See [`queries`](./messages-and-queries.md#queries) for more information.
|
||||
- The `req` itself is primarily used to retrieve arguments if they are too large to fit in the `path`. This is done using the `Data` field of `req`.
|
||||
- The [`Context`](../core/context.md) contains all the necessary information needed to process the `query`, as well as a cache-wrapped copy of the latest state. It is primarily used by the [`keeper`](./keeper.md) to access the state.
|
||||
- The [`Context`](../core/context.md) contains all the necessary information needed to process the `query`, as well as a branch of the latest state. It is primarily used by the [`keeper`](./keeper.md) to access the state.
|
||||
- The result `res` returned to `BaseApp`, marshalled using the application's [`codec`](../core/encoding.md).
|
||||
|
||||
## Implementation of a module query service
|
||||
|
||||
@ -126,15 +126,16 @@ is the canonical state of the application and the volatile states, `checkState`
|
||||
are used to handle state transitions in-between the main state made during [`Commit`](#commit).
|
||||
|
||||
Internally, there is only a single `CommitMultiStore` which we refer to as the main or root state.
|
||||
From this root state, we derive two volatile state through a mechanism called cache-wrapping. The
|
||||
types can be illustrated as follows:
|
||||
From this root state, we derive two volatile state through a mechanism called _store branching_ (performed by `CacheWrap` function).
|
||||
The types can be illustrated as follows:
|
||||
|
||||

|
||||
|
||||
### InitChain State Updates
|
||||
|
||||
During `InitChain`, the two volatile states, `checkState` and `deliverState` are set by cache-wrapping
|
||||
the root `CommitMultiStore`. Any subsequent reads and writes happen on cached versions of the `CommitMultiStore`.
|
||||
During `InitChain`, the two volatile states, `checkState` and `deliverState` are set by branching
|
||||
the root `CommitMultiStore`. Any subsequent reads and writes happen on branched versions of the `CommitMultiStore`.
|
||||
To avoid unnecessary roundtrip to the main state, all reads to the branched store are cached.
|
||||
|
||||

|
||||
|
||||
@ -142,8 +143,8 @@ the root `CommitMultiStore`. Any subsequent reads and writes happen on cached ve
|
||||
|
||||
During `CheckTx`, the `checkState`, which is based off of the last committed state from the root
|
||||
store, is used for any reads and writes. Here we only execute the `AnteHandler` and verify a service router
|
||||
exists for every message in the transaction. Note, when we execute the `AnteHandler`, we cache-wrap
|
||||
the already cache-wrapped `checkState`. This has the side effect that if the `AnteHandler` fails,
|
||||
exists for every message in the transaction. Note, when we execute the `AnteHandler`, we branch
|
||||
the already branched `checkState`. This has the side effect that if the `AnteHandler` fails,
|
||||
the state transitions won't be reflected in the `checkState` -- i.e. `checkState` is only updated on
|
||||
success.
|
||||
|
||||
@ -152,7 +153,7 @@ success.
|
||||
### BeginBlock State Updates
|
||||
|
||||
During `BeginBlock`, the `deliverState` is set for use in subsequent `DeliverTx` ABCI messages. The
|
||||
`deliverState` is based off of the last committed state from the root store and is cache-wrapped.
|
||||
`deliverState` is based off of the last committed state from the root store and is branched.
|
||||
Note, the `deliverState` is set to `nil` on [`Commit`](#commit).
|
||||
|
||||

|
||||
@ -161,7 +162,7 @@ Note, the `deliverState` is set to `nil` on [`Commit`](#commit).
|
||||
|
||||
The state flow for `DeliverTx` is nearly identical to `CheckTx` except state transitions occur on
|
||||
the `deliverState` and messages in a transaction are executed. Similarly to `CheckTx`, state transitions
|
||||
occur on a doubly cache-wrapped state -- `deliverState`. Successful message execution results in
|
||||
occur on a doubly branched state -- `deliverState`. Successful message execution results in
|
||||
writes being committed to `deliverState`. Note, if message execution fails, state transitions from
|
||||
the AnteHandler are persisted.
|
||||
|
||||
@ -283,7 +284,7 @@ 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:
|
||||
|
||||
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 `Msg` in the transaction, route to the appropriate module's [`Msg` service](../building-modules/msg-services.md). Additional _stateful_ checks are performed, and the cache-wrapped multistore held in `deliverState`'s `context` is updated by the module's `keeper`. If the `Msg` service returns successfully, the cache-wrapped multistore held in `context` is written to `deliverState` `CacheMultiStore`.
|
||||
2. For each `Msg` in the transaction, route to the appropriate module's [`Msg` service](../building-modules/msg-services.md). Additional _stateful_ checks are performed, and the branched multistore held in `deliverState`'s `context` is updated by the module's `keeper`. If the `Msg` service returns successfully, the branched 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:
|
||||
|
||||
@ -308,17 +309,17 @@ At any point, if `GasConsumed > GasWanted`, the function returns with `Code != 0
|
||||
|
||||
`RunTx` is called from `CheckTx`/`DeliverTx` to handle the transaction, with `runTxModeCheck` or `runTxModeDeliver` as parameter to differentiate between the two modes of execution. Note that when `RunTx` receives a transaction, it has already been decoded.
|
||||
|
||||
The first thing `RunTx` does upon being called is to retrieve the `context`'s `CacheMultiStore` by calling the `getContextForTx()` function with the appropriate mode (either `runTxModeCheck` or `runTxModeDeliver`). This `CacheMultiStore` is a cached version of the main store instantiated during `BeginBlock` for `DeliverTx` and during the `Commit` of the previous block for `CheckTx`. After that, two `defer func()` are called for [`gas`](../basics/gas-fees.md) management. They are executed when `runTx` returns and make sure `gas` is actually consumed, and will throw errors, if any.
|
||||
The first thing `RunTx` does upon being called is to retrieve the `context`'s `CacheMultiStore` by calling the `getContextForTx()` function with the appropriate mode (either `runTxModeCheck` or `runTxModeDeliver`). This `CacheMultiStore` is a branch of the main store, with cache functionality (for query requests), instantiated during `BeginBlock` for `DeliverTx` and during the `Commit` of the previous block for `CheckTx`. After that, two `defer func()` are called for [`gas`](../basics/gas-fees.md) management. They are executed when `runTx` returns and make sure `gas` is actually consumed, and will throw errors, if any.
|
||||
|
||||
After that, `RunTx()` calls `ValidateBasic()` on each `Msg`in the `Tx`, which runs preliminary _stateless_ validity checks. If any `Msg` fails to pass `ValidateBasic()`, `RunTx()` returns with an error.
|
||||
|
||||
Then, the [`anteHandler`](#antehandler) of the application is run (if it exists). In preparation of this step, both the `checkState`/`deliverState`'s `context` and `context`'s `CacheMultiStore` are cached-wrapped using the `cacheTxContext()` function.
|
||||
Then, the [`anteHandler`](#antehandler) of the application is run (if it exists). In preparation of this step, both the `checkState`/`deliverState`'s `context` and `context`'s `CacheMultiStore` are branched using the `cacheTxContext()` function.
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/baseapp/baseapp.go#L623-L630
|
||||
|
||||
This allows `RunTx` not to commit the changes made to the state during the execution of `anteHandler` if it ends up failing. It also prevents the module implementing the `anteHandler` from writing to state, which is an important part of the [object-capabilities](./ocap.md) of the Cosmos SDK.
|
||||
|
||||
Finally, the [`RunMsgs()`](#runmsgs) function is called to process the `Msg`s in the `Tx`. In preparation of this step, just like with the `anteHandler`, both the `checkState`/`deliverState`'s `context` and `context`'s `CacheMultiStore` are cached-wrapped using the `cacheTxContext()` function.
|
||||
Finally, the [`RunMsgs()`](#runmsgs) function is called to process the `Msg`s in the `Tx`. In preparation of this step, just like with the `anteHandler`, both the `checkState`/`deliverState`'s `context` and `context`'s `CacheMultiStore` are branched using the `cacheTxContext()` function.
|
||||
|
||||
### AnteHandler
|
||||
|
||||
@ -373,7 +374,7 @@ The [`EndBlock` ABCI message](#https://tendermint.com/docs/app-dev/abci-spec.htm
|
||||
|
||||
The [`Commit` ABCI message](https://tendermint.com/docs/app-dev/abci-spec.html#commit) is sent from the underlying Tendermint engine after the full-node has received _precommits_ from 2/3+ of validators (weighted by voting power). On the `BaseApp` end, the `Commit(res abci.ResponseCommit)` function is implemented to commit all the valid state transitions that occured during `BeginBlock`, `DeliverTx` and `EndBlock` and to reset state for the next block.
|
||||
|
||||
To commit state-transitions, the `Commit` function calls the `Write()` function on `deliverState.ms`, where `deliverState.ms` is a cached multistore of the main store `app.cms`. Then, the `Commit` function sets `checkState` to the latest header (obtbained from `deliverState.ctx.BlockHeader`) and `deliverState` to `nil`.
|
||||
To commit state-transitions, the `Commit` function calls the `Write()` function on `deliverState.ms`, where `deliverState.ms` is a branched multistore of the main store `app.cms`. Then, the `Commit` function sets `checkState` to the latest header (obtbained from `deliverState.ctx.BlockHeader`) and `deliverState` to `nil`.
|
||||
|
||||
Finally, `Commit` returns the hash of the commitment of `app.cms` back to the underlying consensus engine. This hash is used as a reference in the header of the next block.
|
||||
|
||||
@ -390,7 +391,7 @@ Each Tendermint `query` comes with a `path`, which is a `string` which denotes w
|
||||
- Application-related queries like querying the application's version, which are served via the `handleQueryApp` method.
|
||||
- Direct queries to the multistore, which are served by the `handlerQueryStore` method. These direct queryeis are different from custom queries which go through `app.queryRouter`, and are mainly used by third-party service provider like block explorers.
|
||||
- P2P queries, which are served via the `handleQueryP2P` method. These queries return either `app.addrPeerFilter` or `app.ipPeerFilter` that contain the list of peers filtered by address or IP respectively. These lists are first initialized via `options` in `BaseApp`'s [constructor](#constructor).
|
||||
- Custom queries, which encompass legacy queries (before the introduction of gRPC queries), are served via the `handleQueryCustom` method. The `handleQueryCustom` cache-wraps the multistore before using the `queryRoute` obtained from `app.queryRouter` to map the query to the appropriate module's [legacy `querier`](../building-modules/query-services.md#legacy-queriers).
|
||||
- Custom queries, which encompass legacy queries (before the introduction of gRPC queries), are served via the `handleQueryCustom` method. The `handleQueryCustom` branches the multistore before using the `queryRoute` obtained from `app.queryRouter` to map the query to the appropriate module's [legacy `querier`](../building-modules/query-services.md#legacy-queriers).
|
||||
|
||||
## Next {hide}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ order: 3
|
||||
|
||||
# Context
|
||||
|
||||
The `context` is a data structure intended to be passed from function to function that carries information about the current state of the application. It holds a cached copy of the entire state as well as useful objects and information like `gasMeter`, `block height`, `consensus parameters` and more. {synopsis}
|
||||
The `context` is a data structure intended to be passed from function to function that carries information about the current state of the application. It provides an access to a branched storage (a safe branch of the entire state) as well as useful objects and information like `gasMeter`, `block height`, `consensus parameters` and more. {synopsis}
|
||||
|
||||
## Pre-requisites Readings
|
||||
|
||||
@ -54,21 +54,23 @@ childCtx = parentCtx.WithBlockHeader(header)
|
||||
The [Golang Context Package](https://golang.org/pkg/context) documentation instructs developers to
|
||||
explicitly pass a context `ctx` as the first argument of a process.
|
||||
|
||||
## Cache Wrapping
|
||||
## Store branching
|
||||
|
||||
The `Context` contains a `MultiStore`, which allows for cache-wrapping functionality: a `CacheMultiStore`
|
||||
where each `KVStore` is is wrapped with an ephemeral cache. Processes are free to write changes to
|
||||
the `CacheMultiStore`, then write the changes back to the original state or disregard them if something
|
||||
The `Context` contains a `MultiStore`, which allows for branchinig and caching functionality using `CacheMultiStore`
|
||||
(queries in `CacheMultiStore` are cached to avoid future round trips).
|
||||
Each `KVStore` is branched in a safe and isolated ephemeral storage. Processes are free to write changes to
|
||||
the `CacheMultiStore`. If a state-transition sequence is performed without issue, the store branch can
|
||||
be committed to the underlying store at the end of the sequence or disregard them if something
|
||||
goes wrong. The pattern of usage for a Context is as follows:
|
||||
|
||||
1. A process receives a Context `ctx` from its parent process, which provides information needed to
|
||||
perform the process.
|
||||
2. The `ctx.ms` is **cache wrapped**, i.e. a cached copy of the [multistore](./store.md#multistore) is made so that the process can make changes to the state as it executes, without changing the original`ctx.ms`. This is useful to protect the underlying multistore in case the changes need to be reverted at some point in the execution.
|
||||
2. The `ctx.ms` is a **branched store**, i.e. a branch of the [multistore](./store.md#multistore) is made so that the process can make changes to the state as it executes, without changing the original`ctx.ms`. This is useful to protect the underlying multistore in case the changes need to be reverted at some point in the execution.
|
||||
3. The process may read and write from `ctx` as it is executing. It may call a subprocess and pass
|
||||
`ctx` to it as needed.
|
||||
4. When a subprocess returns, it checks if the result is a success or failure. If a failure, nothing
|
||||
needs to be done - the cache wrapped `ctx` is simply discarded. If successful, the changes made to
|
||||
the cache-wrapped `MultiStore` can be committed to the original `ctx.ms` via `Write()`.
|
||||
needs to be done - the branch `ctx` is simply discarded. If successful, the changes made to
|
||||
the `CacheMultiStore` can be committed to the original `ctx.ms` via `Write()`.
|
||||
|
||||
For example, here is a snippet from the [`runTx`](./baseapp.md#runtx-and-runmsgs) function in
|
||||
[`baseapp`](./baseapp.md):
|
||||
@ -90,12 +92,12 @@ if result.IsOK() {
|
||||
Here is the process:
|
||||
|
||||
1. Prior to calling `runMsgs` on the message(s) in the transaction, it uses `app.cacheTxContext()`
|
||||
to cache-wrap the context and multistore.
|
||||
2. The cache-wrapped context, `runMsgCtx`, is used in `runMsgs` to return a result.
|
||||
to branch and cache the context and multistore.
|
||||
2. `runMsgCtx` - the context with branched store, is used in `runMsgs` to return a result.
|
||||
3. If the process is running in [`checkTxMode`](./baseapp.md#checktx), there is no need to write the
|
||||
changes - the result is returned immediately.
|
||||
4. If the process is running in [`deliverTxMode`](./baseapp.md#delivertx) and the result indicates
|
||||
a successful run over all the messages, the cached multistore is written back to the original.
|
||||
a successful run over all the messages, the branched multistore is written back to the original.
|
||||
|
||||
## Next {hide}
|
||||
|
||||
|
||||
@ -56,15 +56,15 @@ The Cosmos SDK comes with a large set of stores to persist the state of applicat
|
||||
|
||||
### Store Interface
|
||||
|
||||
At its very core, a Cosmos SDK `store` is an object that holds a `CacheWrapper` and implements a `GetStoreType()` method:
|
||||
At its very core, a Cosmos SDK `store` is an object that holds a `CacheWrapper` and has a `GetStoreType()` method:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/store/types/store.go#L15-L18
|
||||
|
||||
The `GetStoreType` is a simple method that returns the type of store, whereas a `CacheWrapper` is a simple interface that specifies cache-wrapping and `Write` methods:
|
||||
The `GetStoreType` is a simple method that returns the type of store, whereas a `CacheWrapper` is a simple interface that implements store read caching and write branching through `Write` method:
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/store/types/store.go#L240-L264
|
||||
|
||||
Cache-wrapping is used ubiquitously in the Cosmos SDK and required to be implemented on every store type. A cache-wrapper creates a light snapshot of a store that can be passed around and updated without affecting the main underlying store. This is used to trigger temporary state-transitions that may be reverted later should an error occur. If a state-transition sequence is performed without issue, the cached store can be committed to the underlying store at the end of the sequence.
|
||||
Branching and cache is used ubiquitously in the Cosmos SDK and required to be implemented on every store type. A storage branch creates an isolated, ephemeral branch of a store that can be passed around and updated without affecting the main underlying store. This is used to trigger temporary state-transitions that may be reverted later should an error occur. Read more about it in [context](./context.md#Store-branching)
|
||||
|
||||
### Commit Store
|
||||
|
||||
@ -88,7 +88,7 @@ Each Cosmos SDK application holds a multistore at its root to persist its state.
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/store/types/store.go#L104-L133
|
||||
|
||||
If tracing is enabled, then cache-wrapping the multistore will wrap all the underlying `KVStore` in [`TraceKv.Store`](#tracekv-store) before caching them.
|
||||
If tracing is enabled, then branching the multistore will firstly wrap all the underlying `KVStore` in [`TraceKv.Store`](#tracekv-store).
|
||||
|
||||
### CommitMultiStore
|
||||
|
||||
@ -104,11 +104,11 @@ The `rootMulti.Store` is a base-layer multistore built around a `db` on top of w
|
||||
|
||||
### CacheMultiStore
|
||||
|
||||
Whenever the `rootMulti.Store` needs to be cached-wrapped, a [`cachemulti.Store`](https://github.com/cosmos/cosmos-sdk/blob/master/store/cachemulti/store.go) is used.
|
||||
Whenever the `rootMulti.Store` needs to be branched, a [`cachemulti.Store`](https://github.com/cosmos/cosmos-sdk/blob/master/store/cachemulti/store.go) is used.
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/store/cachemulti/store.go#L17-L28
|
||||
|
||||
`cachemulti.Store` cache wraps all substores in its constructor and hold them in `Store.stores`. `Store.GetKVStore()` returns the store from `Store.stores`, and `Store.Write()` recursively calls `CacheWrap.Write()` on all the substores.
|
||||
`cachemulti.Store` branches all substores (creates a virtual store for each substore) in its constructor and hold them in `Store.stores`. Moreover cachese all read queries. `Store.GetKVStore()` returns the store from `Store.stores`, and `Store.Write()` recursively calls `CacheWrap.Write()` on all the substores.
|
||||
|
||||
## Base-layer KVStores
|
||||
|
||||
@ -172,19 +172,19 @@ Transient stores are typically accessed via the [`context`](./context.md) via th
|
||||
|
||||
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/store/cachekv/store.go#L27-L34
|
||||
|
||||
This is the type used whenever an IAVL Store needs to be cache-wrapped (typically when setting value that might be reverted later).
|
||||
This is the type used whenever an IAVL Store needs to be branched to create an isolated store (typically when we need to mutate a state that might be reverted later).
|
||||
|
||||
#### `Get`
|
||||
|
||||
`Store.Get()` checks `Store.cache` first in order to find if there is any cached value associated with the key. If the value exists, the function returns it. If not, the function calls `Store.parent.Get()`, sets the key-value pair to the `Store.cache`, and returns it.
|
||||
`Store.Get()` firstly checks if `Store.cache` has an associated value with the key. If the value exists, the function returns it. If not, the function calls `Store.parent.Get()`, caches the result in `Store.cache`, and returns it.
|
||||
|
||||
#### `Set`
|
||||
|
||||
`Store.Set()` sets the key-value pair to the `Store.cache`. `cValue` has the field dirty bool which indicates whether the cached value is different from the underlying value. When `Store.Set()` cache new pair, the `cValue.dirty` is set `true` so when `Store.Write()` is called it can be written to the underlying store.
|
||||
`Store.Set()` sets the key-value pair to the `Store.cache`. `cValue` has the field dirty bool which indicates whether the cached value is different from the underlying value. When `Store.Set()` cachees a new pair, the `cValue.dirty` is set `true` so when `Store.Write()` is called it can be written to the underlying store.
|
||||
|
||||
#### `Iterator`
|
||||
|
||||
`Store.Iterator()` have to traverse on both caches items and the original items. In `Store.iterator()`, two iterators are generated for each of them, and merged. `memIterator` is essentially a slice of the `KVPairs`, used for cached items. `mergeIterator` is a combination of two iterators, where traverse happens ordered on both iterators.
|
||||
`Store.Iterator()` have to traverse on both cached items and the original items. In `Store.iterator()`, two iterators are generated for each of them, and merged. `memIterator` is essentially a slice of the `KVPairs`, used for cached items. `mergeIterator` is a combination of two iterators, where traverse happens ordered on both iterators.
|
||||
|
||||
### `GasKv` Store
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ type Store struct {
|
||||
}
|
||||
```
|
||||
|
||||
`cachemulti.Store` cache wraps all substores in its constructor and hold them in `Store.stores`. `Store.GetKVStore()` returns the store from `Store.stores`, and `Store.Write()` recursively calls `CacheWrap.Write()` on the substores.
|
||||
`cachemulti.Store` branches all substores in its constructor and hold them in `Store.stores`. `Store.GetKVStore()` returns the store from `Store.stores`, and `Store.Write()` recursively calls `CacheWrap.Write()` on the substores.
|
||||
|
||||
## DBAdapter
|
||||
|
||||
|
||||
2
store/cache/cache.go
vendored
2
store/cache/cache.go
vendored
@ -89,7 +89,7 @@ func (cmgr *CommitKVStoreCacheManager) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
// CacheWrap returns the inter-block cache as a cache-wrapped CommitKVStore.
|
||||
// CacheWrap implements the CacheWrapper interface
|
||||
func (ckv *CommitKVStoreCache) CacheWrap() types.CacheWrap {
|
||||
return cachekv.NewStore(ckv)
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ type Store struct {
|
||||
|
||||
var _ types.CacheKVStore = (*Store)(nil)
|
||||
|
||||
// NewStore creates a new Store object
|
||||
func NewStore(parent types.KVStore) *Store {
|
||||
return &Store{
|
||||
cache: make(map[string]*cValue),
|
||||
@ -44,12 +45,12 @@ func NewStore(parent types.KVStore) *Store {
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Store.
|
||||
// GetStoreType implements Store.
|
||||
func (store *Store) GetStoreType() types.StoreType {
|
||||
return store.parent.GetStoreType()
|
||||
}
|
||||
|
||||
// Implements types.KVStore.
|
||||
// Get implements types.KVStore.
|
||||
func (store *Store) Get(key []byte) (value []byte) {
|
||||
store.mtx.Lock()
|
||||
defer store.mtx.Unlock()
|
||||
@ -68,7 +69,7 @@ func (store *Store) Get(key []byte) (value []byte) {
|
||||
return value
|
||||
}
|
||||
|
||||
// Implements types.KVStore.
|
||||
// Set implements types.KVStore.
|
||||
func (store *Store) Set(key []byte, value []byte) {
|
||||
store.mtx.Lock()
|
||||
defer store.mtx.Unlock()
|
||||
@ -80,13 +81,13 @@ func (store *Store) Set(key []byte, value []byte) {
|
||||
store.setCacheValue(key, value, false, true)
|
||||
}
|
||||
|
||||
// Implements types.KVStore.
|
||||
// Has implements types.KVStore.
|
||||
func (store *Store) Has(key []byte) bool {
|
||||
value := store.Get(key)
|
||||
return value != nil
|
||||
}
|
||||
|
||||
// Implements types.KVStore.
|
||||
// Delete implements types.KVStore.
|
||||
func (store *Store) Delete(key []byte) {
|
||||
store.mtx.Lock()
|
||||
defer store.mtx.Unlock()
|
||||
@ -135,10 +136,7 @@ func (store *Store) Write() {
|
||||
store.sortedCache = list.New()
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// To cache-wrap this Store further.
|
||||
|
||||
// Implements CacheWrapper.
|
||||
// CacheWrap implements CacheWrapper.
|
||||
func (store *Store) CacheWrap() types.CacheWrap {
|
||||
return NewStore(store)
|
||||
}
|
||||
@ -151,12 +149,12 @@ func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types
|
||||
//----------------------------------------
|
||||
// Iteration
|
||||
|
||||
// Implements types.KVStore.
|
||||
// Iterator implements types.KVStore.
|
||||
func (store *Store) Iterator(start, end []byte) types.Iterator {
|
||||
return store.iterator(start, end, true)
|
||||
}
|
||||
|
||||
// Implements types.KVStore.
|
||||
// ReverseIterator implements types.KVStore.
|
||||
func (store *Store) ReverseIterator(start, end []byte) types.Iterator {
|
||||
return store.iterator(start, end, false)
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
//----------------------------------------
|
||||
// Store
|
||||
|
||||
// Store holds many cache-wrapped stores.
|
||||
// Store holds many branched stores.
|
||||
// Implements MultiStore.
|
||||
// NOTE: a Store (and MultiStores in general) should never expose the
|
||||
// keys for the substores.
|
||||
@ -31,7 +31,7 @@ var _ types.CacheMultiStore = Store{}
|
||||
|
||||
// NewFromKVStore creates a new Store object from a mapping of store keys to
|
||||
// CacheWrapper objects and a KVStore as the database. Each CacheWrapper store
|
||||
// is cache-wrapped.
|
||||
// is a branched store.
|
||||
func NewFromKVStore(
|
||||
store types.KVStore, stores map[types.StoreKey]types.CacheWrapper,
|
||||
keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext,
|
||||
@ -56,7 +56,7 @@ func NewFromKVStore(
|
||||
}
|
||||
|
||||
// NewStore creates a new Store object from a mapping of store keys to
|
||||
// CacheWrapper objects. Each CacheWrapper store is cache-wrapped.
|
||||
// CacheWrapper objects. Each CacheWrapper store is a branched store.
|
||||
func NewStore(
|
||||
db dbm.DB, stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey,
|
||||
traceWriter io.Writer, traceContext types.TraceContext,
|
||||
@ -136,7 +136,7 @@ func (cms Store) CacheMultiStore() types.CacheMultiStore {
|
||||
// TODO: The store implementation can possibly be modified to support this as it
|
||||
// seems safe to load previous versions (heights).
|
||||
func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, error) {
|
||||
panic("cannot cache-wrap cached multi-store with a version")
|
||||
panic("cannot branch cached multi-store with a version")
|
||||
}
|
||||
|
||||
// GetStore returns an underlying Store by key.
|
||||
|
||||
@ -75,7 +75,7 @@ func (Store) GetStoreType() types.StoreType {
|
||||
return types.StoreTypeDB
|
||||
}
|
||||
|
||||
// CacheWrap cache wraps the underlying store.
|
||||
// CacheWrap branches the underlying store.
|
||||
func (dsa Store) CacheWrap() types.CacheWrap {
|
||||
return cachekv.NewStore(dsa)
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ func (s Store) GetStoreType() types.StoreType {
|
||||
return types.StoreTypeMemory
|
||||
}
|
||||
|
||||
// CacheWrap cache wraps the underlying store.
|
||||
// CacheWrap branches the underlying store.
|
||||
func (s Store) CacheWrap() types.CacheWrap {
|
||||
return cachekv.NewStore(s)
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ const (
|
||||
)
|
||||
|
||||
// Store is composed of many CommitStores. Name contrasts with
|
||||
// cacheMultiStore which is for cache-wrapping other MultiStores. It implements
|
||||
// cacheMultiStore which is used for branching other MultiStores. It implements
|
||||
// the CommitMultiStore interface.
|
||||
type Store struct {
|
||||
db dbm.DB
|
||||
@ -404,7 +404,7 @@ func (rs *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.Cac
|
||||
return rs.CacheWrap()
|
||||
}
|
||||
|
||||
// CacheMultiStore cache-wraps the multi-store and returns a CacheMultiStore.
|
||||
// CacheMultiStore creates ephemeral branch of the multi-store and returns a CacheMultiStore.
|
||||
// It implements the MultiStore interface.
|
||||
func (rs *Store) CacheMultiStore() types.CacheMultiStore {
|
||||
stores := make(map[types.StoreKey]types.CacheWrapper)
|
||||
@ -841,7 +841,7 @@ func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID
|
||||
if rs.interBlockCache != nil {
|
||||
// Wrap and get a CommitKVStore with inter-block caching. Note, this should
|
||||
// only wrap the primary CommitKVStore, not any store that is already
|
||||
// cache-wrapped as that will create unexpected behavior.
|
||||
// branched as that will create unexpected behavior.
|
||||
store = rs.interBlockCache.GetStoreCache(key, store)
|
||||
}
|
||||
|
||||
|
||||
@ -161,14 +161,14 @@ func (tkv *Store) GetStoreType() types.StoreType {
|
||||
return tkv.parent.GetStoreType()
|
||||
}
|
||||
|
||||
// CacheWrap implements the KVStore interface. It panics as a Store
|
||||
// cannot be cache wrapped.
|
||||
// CacheWrap implements the KVStore interface. It panics because a Store
|
||||
// cannot be branched.
|
||||
func (tkv *Store) CacheWrap() types.CacheWrap {
|
||||
panic("cannot CacheWrap a Store")
|
||||
}
|
||||
|
||||
// CacheWrapWithTrace implements the KVStore interface. It panics as a
|
||||
// Store cannot be cache wrapped.
|
||||
// Store cannot be branched.
|
||||
func (tkv *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
|
||||
panic("cannot CacheWrapWithTrace a Store")
|
||||
}
|
||||
|
||||
@ -104,12 +104,12 @@ func (s *StoreUpgrades) RenamedFrom(key string) string {
|
||||
type MultiStore interface {
|
||||
Store
|
||||
|
||||
// Cache wrap MultiStore.
|
||||
// Branches MultiStore into a cached storage object.
|
||||
// NOTE: Caller should probably not call .Write() on each, but
|
||||
// call CacheMultiStore.Write().
|
||||
CacheMultiStore() CacheMultiStore
|
||||
|
||||
// CacheMultiStoreWithVersion cache-wraps the underlying MultiStore where
|
||||
// CacheMultiStoreWithVersion branches the underlying MultiStore where
|
||||
// each stored is loaded at a specific version (height).
|
||||
CacheMultiStoreWithVersion(version int64) (CacheMultiStore, error)
|
||||
|
||||
@ -138,7 +138,7 @@ type CacheMultiStore interface {
|
||||
Write() // Writes operations to underlying KVStore
|
||||
}
|
||||
|
||||
// A non-cache MultiStore.
|
||||
// CommitMultiStore is an interface for a MultiStore without cache capabilities.
|
||||
type CommitMultiStore interface {
|
||||
Committer
|
||||
MultiStore
|
||||
@ -218,12 +218,12 @@ type KVStore interface {
|
||||
ReverseIterator(start, end []byte) Iterator
|
||||
}
|
||||
|
||||
// Alias iterator to db's Iterator for convenience.
|
||||
// Iterator is an alias db's Iterator for convenience.
|
||||
type Iterator = dbm.Iterator
|
||||
|
||||
// CacheKVStore cache-wraps a KVStore. After calling .Write() on
|
||||
// the CacheKVStore, all previously created CacheKVStores on the
|
||||
// object expire.
|
||||
// CacheKVStore branches a KVStore and provides read cache functionality.
|
||||
// After calling .Write() on the CacheKVStore, all previously created
|
||||
// CacheKVStores on the object expire.
|
||||
type CacheKVStore interface {
|
||||
KVStore
|
||||
|
||||
@ -231,7 +231,7 @@ type CacheKVStore interface {
|
||||
Write()
|
||||
}
|
||||
|
||||
// Stores of MultiStore must implement CommitStore.
|
||||
// CommitKVStore is an interface for MultiStore.
|
||||
type CommitKVStore interface {
|
||||
Committer
|
||||
KVStore
|
||||
@ -240,9 +240,9 @@ type CommitKVStore interface {
|
||||
//----------------------------------------
|
||||
// CacheWrap
|
||||
|
||||
// CacheWrap makes the most appropriate cache-wrap. For example,
|
||||
// IAVLStore.CacheWrap() returns a CacheKVStore. CacheWrap should not return
|
||||
// a Committer, since Commit cache-wraps make no sense. It can return KVStore,
|
||||
// CacheWrap is the most appropriate interface for store ephemeral branching and cache.
|
||||
// For example, IAVLStore.CacheWrap() returns a CacheKVStore. CacheWrap should not return
|
||||
// a Committer, since Commit ephemeral store make no sense. It can return KVStore,
|
||||
// HeapStore, SpaceStore, etc.
|
||||
type CacheWrap interface {
|
||||
// Write syncs with the underlying store.
|
||||
@ -256,10 +256,10 @@ type CacheWrap interface {
|
||||
}
|
||||
|
||||
type CacheWrapper interface {
|
||||
// CacheWrap cache wraps.
|
||||
// CacheWrap branches a store.
|
||||
CacheWrap() CacheWrap
|
||||
|
||||
// CacheWrapWithTrace cache wraps with tracing enabled.
|
||||
// CacheWrapWithTrace branches a store with tracing enabled.
|
||||
CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap
|
||||
}
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ func queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery,
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
// cache-wrap context as to not persist state changes during querying
|
||||
// branch the context to isolate state changes
|
||||
ctx, _ = ctx.CacheContext()
|
||||
|
||||
val := k.stakingKeeper.Validator(ctx, params.ValidatorAddress)
|
||||
@ -163,7 +163,7 @@ func queryDelegatorTotalRewards(ctx sdk.Context, _ []string, req abci.RequestQue
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
// cache-wrap context as to not persist state changes during querying
|
||||
// branch the context to isolate state changes
|
||||
ctx, _ = ctx.CacheContext()
|
||||
|
||||
total := sdk.DecCoins{}
|
||||
@ -201,7 +201,7 @@ func queryDelegatorValidators(ctx sdk.Context, _ []string, req abci.RequestQuery
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
// cache-wrap context as to not persist state changes during querying
|
||||
// branch the context to isolate state changes
|
||||
ctx, _ = ctx.CacheContext()
|
||||
|
||||
var validators []sdk.ValAddress
|
||||
@ -229,7 +229,7 @@ func queryDelegatorWithdrawAddress(ctx sdk.Context, _ []string, req abci.Request
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
// cache-wrap context as to not persist state changes during querying
|
||||
// branch the context to isolate state changes
|
||||
ctx, _ = ctx.CacheContext()
|
||||
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, params.DelegatorAddress)
|
||||
|
||||
|
||||
@ -15,9 +15,9 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, content types.Content) (typ
|
||||
return types.Proposal{}, sdkerrors.Wrap(types.ErrNoProposalHandlerExists, content.ProposalRoute())
|
||||
}
|
||||
|
||||
// Execute the proposal content in a cache-wrapped context to validate the
|
||||
// actual parameter changes before the proposal proceeds through the
|
||||
// governance process. State is not persisted.
|
||||
// Execute the proposal content in a new context branch (with branched store)
|
||||
// to validate the actual parameter changes before the proposal proceeds
|
||||
// through the governance process. State is not persisted.
|
||||
cacheCtx, _ := ctx.CacheContext()
|
||||
handler := keeper.router.GetRoute(content.ProposalRoute())
|
||||
if err := handler(cacheCtx, content); err != nil {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user