# Store

A store is a data structure that holds the state of the application.

# Pre-requisite Readings

# Introduction to Cosmos SDK Stores

The Cosmos SDK comes with a large set of stores to persist the state of applications. By default, the main store of Cosmos SDK applications is a multistore, i.e. a store of stores. Developers can add any number of key-value stores to the multistore, depending on their application needs. The multistore exists to support the modularity of the Cosmos SDK, as it lets each module declare and manage their own subset of the state. Key-value stores in the multistore can only be accessed with a specific capability key, which is typically held in the keeper of the module that declared the store.

Copy +-----------------------------------------------------+ | | | +--------------------------------------------+ | | | | | | | KVStore 1 - Manage by keeper of Module 1 | | | | | | +--------------------------------------------+ | | | | +--------------------------------------------+ | | | | | | | KVStore 2 - Manage by keeper of Module 2 | | | | | | | +--------------------------------------------+ | | | | +--------------------------------------------+ | | | | | | | KVStore 3 - Manage by keeper of Module 2 | | | | | | | +--------------------------------------------+ | | | | +--------------------------------------------+ | | | | | | | KVStore 4 - Manage by keeper of Module 3 | | | | | | | +--------------------------------------------+ | | | | +--------------------------------------------+ | | | | | | | KVStore 5 - Manage by keeper of Module 4 | | | | | | | +--------------------------------------------+ | | | | Main Multistore | | | +-----------------------------------------------------+ Application's State

# Store Interface

At its very core, a Cosmos SDK store is an object that holds a CacheWrapper and has a GetStoreType() method:

Copy type Store interface { GetStoreType() StoreType CacheWrapper }

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:

Copy //---------------------------------------- // CacheWrap // 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. Write() // CacheWrap recursively wraps again. CacheWrap() CacheWrap // CacheWrapWithTrace recursively wraps again with tracing enabled. CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap // CacheWrapWithListeners recursively wraps again with listening enabled CacheWrapWithListeners(storeKey StoreKey, listeners []WriteListener) CacheWrap } type CacheWrapper interface { // CacheWrap branches a store. CacheWrap() CacheWrap // CacheWrapWithTrace branches a store with tracing enabled. CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap // CacheWrapWithListeners recursively wraps again with listening enabled CacheWrapWithListeners(storeKey StoreKey, listeners []WriteListener) CacheWrap }

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

# Commit Store

A commit store is a store that has the ability to commit changes made to the underlying tree or db. The Cosmos SDK differentiates simple stores from commit stores by extending the basic store interfaces with a Committer:

Copy // Stores of MultiStore must implement CommitStore. type CommitStore interface { Committer Store }

The Committer is an interface that defines methods to persist changes to disk:

Copy // something that can persist to disk type Committer interface { Commit() CommitID LastCommitID() CommitID SetPruning(pruningtypes.PruningOptions) GetPruning() pruningtypes.PruningOptions }

The CommitID is a deterministic commit of the state tree. Its hash is returned to the underlying consensus engine and stored in the block header. Note that commit store interfaces exist for various purposes, one of which is to make sure not every object can commit the store. As part of the object-capabilities model of the Cosmos SDK, only baseapp should have the ability to commit stores. For example, this is the reason why the ctx.KVStore() method by which modules typically access stores returns a KVStore and not a CommitKVStore.

The Cosmos SDK comes with many types of stores, the most used being CommitMultiStore, KVStore and GasKv store. Other types of stores include Transient and TraceKV stores.

# Multistore

# Multistore Interface

Each Cosmos SDK application holds a multistore at its root to persist its state. The multistore is a store of KVStores that follows the Multistore interface:

Copy type MultiStore interface { Store // Branches MultiStore into a cached storage object. // NOTE: Caller should probably not call .Write() on each, but // call CacheMultiStore.Write(). CacheMultiStore() CacheMultiStore // CacheMultiStoreWithVersion branches the underlying MultiStore where // each stored is loaded at a specific version (height). CacheMultiStoreWithVersion(version int64) (CacheMultiStore, error) // Convenience for fetching substores. // If the store does not exist, panics. GetStore(StoreKey) Store GetKVStore(StoreKey) KVStore // TracingEnabled returns if tracing is enabled for the MultiStore. TracingEnabled() bool // SetTracer sets the tracer for the MultiStore that the underlying // stores will utilize to trace operations. The modified MultiStore is // returned. SetTracer(w io.Writer) MultiStore // SetTracingContext sets the tracing context for a MultiStore. It is // implied that the caller should update the context when necessary between // tracing operations. The modified MultiStore is returned. SetTracingContext(TraceContext) MultiStore // ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey ListeningEnabled(key StoreKey) bool // AddListeners adds WriteListeners for the KVStore belonging to the provided StoreKey // It appends the listeners to a current set, if one already exists AddListeners(key StoreKey, listeners []WriteListener) }

If tracing is enabled, then branching the multistore will firstly wrap all the underlying KVStore in TraceKv.Store.

# CommitMultiStore

The main type of Multistore used in the Cosmos SDK is CommitMultiStore, which is an extension of the Multistore interface:

Copy // CommitMultiStore is an interface for a MultiStore without cache capabilities. type CommitMultiStore interface { Committer MultiStore snapshottypes.Snapshotter // Mount a store of type using the given db. // If db == nil, the new store will use the CommitMultiStore db. MountStoreWithDB(key StoreKey, typ StoreType, db dbm.DB) // Panics on a nil key. GetCommitStore(key StoreKey) CommitStore // Panics on a nil key. GetCommitKVStore(key StoreKey) CommitKVStore // Load the latest persisted version. Called once after all calls to // Mount*Store() are complete. LoadLatestVersion() error // LoadLatestVersionAndUpgrade will load the latest version, but also // rename/delete/create sub-store keys, before registering all the keys // in order to handle breaking formats in migrations LoadLatestVersionAndUpgrade(upgrades *StoreUpgrades) error // LoadVersionAndUpgrade will load the named version, but also // rename/delete/create sub-store keys, before registering all the keys // in order to handle breaking formats in migrations LoadVersionAndUpgrade(ver int64, upgrades *StoreUpgrades) error // Load a specific persisted version. When you load an old version, or when // the last commit attempt didn't complete, the next commit after loading // must be idempotent (return the same commit id). Otherwise the behavior is // undefined. LoadVersion(ver int64) error // Set an inter-block (persistent) cache that maintains a mapping from // StoreKeys to CommitKVStores. SetInterBlockCache(MultiStorePersistentCache) // SetInitialVersion sets the initial version of the IAVL tree. It is used when // starting a new chain at an arbitrary height. SetInitialVersion(version int64) error // SetIAVLCacheSize sets the cache size of the IAVL tree. SetIAVLCacheSize(size int) }

As for concrete implementation, the [rootMulti.Store] is the go-to implementation of the CommitMultiStore interface.

Copy // Store is composed of many CommitStores. Name contrasts with // cacheMultiStore which is used for branching other MultiStores. It implements // the CommitMultiStore interface. type Store struct { db dbm.DB logger log.Logger lastCommitInfo *types.CommitInfo pruningManager *pruning.Manager iavlCacheSize int storesParams map[types.StoreKey]storeParams stores map[types.StoreKey]types.CommitKVStore keysByName map[string]types.StoreKey lazyLoading bool initialVersion int64 removalMap map[types.StoreKey]bool traceWriter io.Writer traceContext types.TraceContext traceContextMutex sync.Mutex interBlockCache types.MultiStorePersistentCache listeners map[types.StoreKey][]types.WriteListener }

The rootMulti.Store is a base-layer multistore built around a db on top of which multiple KVStores can be mounted, and is the default multistore store used in baseapp.

# CacheMultiStore

Whenever the rootMulti.Store needs to be branched, a cachemulti.Store (opens new window) is used.

Copy //---------------------------------------- // Store // Store holds many branched stores. // Implements MultiStore. // NOTE: a Store (and MultiStores in general) should never expose the // keys for the substores. type Store struct { db types.CacheKVStore stores map[types.StoreKey]types.CacheWrap keys map[string]types.StoreKey traceWriter io.Writer traceContext types.TraceContext listeners map[types.StoreKey][]types.WriteListener }

cachemulti.Store branches all substores (creates a virtual store for each substore) in its constructor and hold them in Store.stores. Moreover caches 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

# KVStore and CommitKVStore Interfaces

A KVStore is a simple key-value store used to store and retrieve data. A CommitKVStore is a KVStore that also implements a Committer. By default, stores mounted in baseapp's main CommitMultiStore are CommitKVStores. The KVStore interface is primarily used to restrict modules from accessing the committer.

Individual KVStores are used by modules to manage a subset of the global state. KVStores can be accessed by objects that hold a specific key. This key should only be exposed to the keeper of the module that defines the store.

CommitKVStores are declared by proxy of their respective key and mounted on the application's multistore in the main application file. In the same file, the key is also passed to the module's keeper that is responsible for managing the store.

Copy // BasicKVStore is a simple interface to get/set data type BasicKVStore interface { // Get returns nil if key doesn't exist. Panics on nil key. Get(key []byte) []byte // Has checks if a key exists. Panics on nil key. Has(key []byte) bool // Set sets the key. Panics on nil key or value. Set(key, value []byte) // Delete deletes the key. Panics on nil key. Delete(key []byte) } // KVStore additionally provides iteration and deletion type KVStore interface { Store BasicKVStore // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. // To iterate over entire domain, use store.Iterator(nil, nil) // CONTRACT: No writes may happen within a domain while an iterator exists over it. // Exceptionally allowed for cachekv.Store, safe to write in the modules. Iterator(start, end []byte) Iterator // Iterator over a domain of keys in descending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. // CONTRACT: No writes may happen within a domain while an iterator exists over it. // Exceptionally allowed for cachekv.Store, safe to write in the modules. ReverseIterator(start, end []byte) Iterator }

Apart from the traditional Get and Set methods, that a KVStore must implement via the BasicKVStore interface; a KVStore must provide an Iterator(start, end) method which returns an Iterator object. It is used to iterate over a range of keys, typically keys that share a common prefix. Below is an example from the bank's module keeper, used to iterate over all account balances:

Copy // IterateAccountBalances iterates over the balances of a single account and // provides the token balance to a callback. If true is returned from the // callback, iteration is halted. func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) { accountStore := k.getAccountStore(ctx, addr) iterator := accountStore.Iterator(nil, nil) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var amount sdk.Int if err := amount.Unmarshal(iterator.Value()); err != nil { panic(err) } if cb(sdk.NewCoin(string(iterator.Key()), amount)) { break } }

# IAVL Store

The default implementation of KVStore and CommitKVStore used in baseapp is the iavl.Store.

Copy // Store Implements types.KVStore and CommitKVStore. type Store struct { tree Tree }

iavl stores are based around an IAVL Tree (opens new window), a self-balancing binary tree which guarantees that:

  • Get and Set operations are O(log n), where n is the number of elements in the tree.
  • Iteration efficiently returns the sorted elements within the range.
  • Each tree version is immutable and can be retrieved even after a commit (depending on the pruning settings).

The documentation on the IAVL Tree is located here (opens new window).

# DbAdapter Store

dbadapter.Store is a adapter for dbm.DB making it fulfilling the KVStore interface.

Copy // Wrapper type for dbm.Db with implementation of KVStore type Store struct { dbm.DB }

dbadapter.Store embeds dbm.DB, meaning most of the KVStore interface functions are implemented. The other functions (mostly miscellaneous) are manually implemented. This store is primarily used within Transient Stores

# Transient Store

Transient.Store is a base-layer KVStore which is automatically discarded at the end of the block.

Copy // Store is a wrapper for a MemDB with Commiter implementation type Store struct { dbadapter.Store }

Transient.Store is a dbadapter.Store with a dbm.NewMemDB(). All KVStore methods are reused. When Store.Commit() is called, a new dbadapter.Store is assigned, discarding previous reference and making it garbage collected.

This type of store is useful to persist information that is only relevant per-block. One example would be to store parameter changes (i.e. a bool set to true if a parameter changed in a block).

Copy // Individual parameter store for each keeper // Transient store persists for a block, so we use it for // recording whether the parameter has been changed or not type Subspace struct { cdc codec.BinaryCodec legacyAmino *codec.LegacyAmino key storetypes.StoreKey // []byte -> []byte, stores parameter tkey storetypes.StoreKey // []byte -> bool, stores parameter change name []byte table KeyTable }

Transient stores are typically accessed via the context via the TransientStore() method:

Copy // TransientStore fetches a TransientStore from the MultiStore. func (c Context) TransientStore(key storetypes.StoreKey) KVStore { return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), storetypes.TransientGasConfig()) }

# KVStore Wrappers

# CacheKVStore

cachekv.Store is a wrapper KVStore which provides buffered writing / cached reading functionalities over the underlying KVStore.

Copy // it means the parent doesn't have the key. (No need to delete upon Write()) type Store struct { mtx sync.Mutex cache map[string]*cValue deleted map[string]struct{} unsortedCache map[string]struct{} sortedCache *dbm.MemDB // always ascending sorted parent types.KVStore }

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() 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() caches 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 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

Cosmos SDK applications use gas to track resources usage and prevent spam. GasKv.Store (opens new window) is a KVStore wrapper that enables automatic gas consumption each time a read or write to the store is made. It is the solution of choice to track storage usage in Cosmos SDK applications.

Copy // Store applies gas tracking to an underlying KVStore. It implements the // KVStore interface. type Store struct { gasMeter types.GasMeter gasConfig types.GasConfig parent types.KVStore }

When methods of the parent KVStore are called, GasKv.Store automatically consumes appropriate amount of gas depending on the Store.gasConfig:

Copy // GasConfig defines gas cost for each operation on KVStores type GasConfig struct { HasCost Gas DeleteCost Gas ReadCostFlat Gas ReadCostPerByte Gas WriteCostFlat Gas WriteCostPerByte Gas IterNextCostFlat Gas }

By default, all KVStores are wrapped in GasKv.Stores when retrieved. This is done in the KVStore() method of the context:

Copy // KVStore fetches a KVStore from the MultiStore. func (c Context) KVStore(key storetypes.StoreKey) KVStore { return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), storetypes.KVGasConfig()) }

In this case, the default gas configuration is used:

Copy // KVGasConfig returns a default gas config for KVStores. func KVGasConfig() GasConfig { return GasConfig{ HasCost: 1000, DeleteCost: 1000, ReadCostFlat: 1000, ReadCostPerByte: 3, WriteCostFlat: 2000, WriteCostPerByte: 30, IterNextCostFlat: 30, } }

# TraceKv Store

tracekv.Store is a wrapper KVStore which provides operation tracing functionalities over the underlying KVStore. It is applied automatically by the Cosmos SDK on all KVStore if tracing is enabled on the parent MultiStore.

Copy type ( // Store implements the KVStore interface with tracing enabled. // Operations are traced on each core KVStore call and written to the // underlying io.writer. // // TODO: Should we use a buffered writer and implement Commit on // Store? Store struct { parent types.KVStore writer io.Writer context types.TraceContext } // operation represents an IO operation operation string // traceOperation implements a traced KVStore operation traceOperation struct { Operation operation `json:"operation"` Key string `json:"key"` Value string `json:"value"` Metadata map[string]interface{} `json:"metadata"` } )

When each KVStore methods are called, tracekv.Store automatically logs traceOperation to the Store.writer. traceOperation.Metadata is filled with Store.context when it is not nil. TraceContext is a map[string]interface{}.

# Prefix Store

prefix.Store is a wrapper KVStore which provides automatic key-prefixing functionalities over the underlying KVStore.

Copy // Store is similar with tendermint/tendermint/libs/db/prefix_db // both gives access only to the limited subset of the store // for convinience or safety type Store struct { parent types.KVStore prefix []byte }

When Store.{Get, Set}() is called, the store forwards the call to its parent, with the key prefixed with the Store.prefix.

When Store.Iterator() is called, it does not simply prefix the Store.prefix, since it does not work as intended. In that case, some of the elements are traversed even they are not starting with the prefix.

# ListenKv Store

listenkv.Store is a wrapper KVStore which provides state listening capabilities over the underlying KVStore. It is applied automatically by the Cosmos SDK on any KVStore whose StoreKey is specified during state streaming configuration. Additional information about state streaming configuration can be found in the store/streaming/README.md (opens new window).

Copy // Store implements the KVStore interface with listening enabled. // Operations are traced on each core KVStore call and written to any of the // underlying listeners with the proper key and operation permissions type Store struct { parent types.KVStore listeners []types.WriteListener parentStoreKey types.StoreKey }

When KVStore.Set or KVStore.Delete methods are called, listenkv.Store automatically writes the operations to the set of Store.listeners.

# New Store package (store/v2alpha1)

The SDK is in the process of transitioning to use the types listed here as the default interface for state storage. At the time of writing, these cannot be used within an application and are not directly compatible with the CommitMultiStore and related types.

These types use the new db sub-module of Cosmos-SDK (github.com/cosmos/cosmos-sdk/db), rather than tmdb (github.com/tendermint/tm-db).

See ADR-040 for the motivations and design specifications of the change.

# BasicKVStore interface

An interface providing only the basic CRUD functionality (Get, Set, Has, and Delete methods), without iteration or caching. This is used to partially expose components of a larger store, such as a root.Store.

# MultiStore

This is the new interface (or, set of interfaces) for the main client store, replacing the role of store/types.MultiStore (v1). There are a few significant differences in behavior compared with v1:

  • Commits are atomic and are performed on the entire store state; individual substores cannot be committed separately and cannot have different version numbers.
  • The store's current version and version history track that of the backing db.Connection. Past versions are accessible read-only.
  • The set of valid substores is defined at initialization and cannot be updated dynamically in an existing store instance.

# CommitMultiStore

This is the main interface for persisent application state, analogous to the original CommitMultiStore.

  • Past version views are accessed with GetVersion, which returns a BasicMultiStore.
  • Substores are accessed with GetKVStore. Trying to get a substore that was not defined at initialization will cause a panic.
  • Close must be called to release the DB resources being used by the store.

# BasicMultiStore

A minimal interface that only allows accessing substores. Note: substores returned by BasicMultiStore.GetKVStore are read-only and will panic on Set or Delete calls.

# Implementation (root.Store)

The canonical implementation of MultiStore is in store/v2alpha1/root. It internally decouples the concerns of state storage and state commitment: values are stored in, and read directly from, the backing key-value database (state storage, or SS), but are also mapped in a logically separate database which generates cryptographic proofs (for state-commitment or SC).

The state-commitment component of each substore is implemented as an independent smt.Store (see below). Internally, each substore is allocated in a logically separate partition within the same backing DB, such that commits apply to the state of all substores. Therefore, views of past versions also include the state of all substores (including SS and SC data).

This store can optionally be configured to use a different backend database instance for SC (e.g., badgerdb for the state storage DB and memdb for the state-commitment DB; see StoreConfig.StateCommitmentDB).

# SMT Store

store/v2alpha1/smt.Store maps values into a Sparse Merkle Tree (SMT), and supports a BasicKVStore interface as well as methods for cryptographic proof generation.

# Next

Learn about encoding