Synopsis
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 amultistore
, 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
Ask AI
+-----------------------------------------------------+
| |
| +--------------------------------------------+ |
| | | |
| | 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 SDKstore
is an object that holds a CacheWrapper
and has a GetStoreType()
method:
Copy
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
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
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
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 aCommitter
:
Copy
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
Committer
is an interface that defines methods to persist changes to disk:
Copy
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
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 ofKVStores
that follows the Multistore
interface:
Copy
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
KVStore
in TraceKv.Store
.
CommitMultiStore
The main type ofMultistore
used in the Cosmos SDK is CommitMultiStore
, which is an extension of the Multistore
interface:
Copy
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
rootMulti.Store
] is the go-to implementation of the CommitMultiStore
interface.
Copy
Ask AI
package rootmulti
import (
"crypto/sha256"
"errors"
"fmt"
"io"
"maps"
"math"
"sort"
"strings"
"sync"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
protoio "github.com/cosmos/gogoproto/io"
gogotypes "github.com/cosmos/gogoproto/types"
iavltree "github.com/cosmos/iavl"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store/cachemulti"
"cosmossdk.io/store/dbadapter"
"cosmossdk.io/store/iavl"
"cosmossdk.io/store/listenkv"
"cosmossdk.io/store/mem"
"cosmossdk.io/store/metrics"
"cosmossdk.io/store/pruning"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/transient"
"cosmossdk.io/store/types"
)
const (
latestVersionKey = "s/latest"
commitInfoKeyFmt = "s/%d" / s/<version>
)
const iavlDisablefastNodeDefault = false
/ keysFromStoreKeyMap returns a slice of keys for the provided map lexically sorted by StoreKey.Name()
func keysFromStoreKeyMap[V any](/docs/sdk/next/documentation/state-storage/m map[types.StoreKey]V) []types.StoreKey {
keys := make([]types.StoreKey, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int)
bool {
ki, kj := keys[i], keys[j]
return ki.Name() < kj.Name()
})
return keys
}
/ 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
iavlDisableFastNode bool
/ iavlSyncPruning should rarely be set to true.
/ The Prune command will automatically set this to true.
/ This allows the prune command to wait for the pruning to finish before returning.
iavlSyncPruning bool
storesParams map[types.StoreKey]storeParams
stores map[types.StoreKey]types.CommitKVStore
keysByName map[string]types.StoreKey
initialVersion int64
removalMap map[types.StoreKey]bool
traceWriter io.Writer
traceContext types.TraceContext
traceContextMutex sync.Mutex
interBlockCache types.MultiStorePersistentCache
listeners map[types.StoreKey]*types.MemoryListener
metrics metrics.StoreMetrics
commitHeader cmtproto.Header
}
var (
_ types.CommitMultiStore = (*Store)(nil)
_ types.Queryable = (*Store)(nil)
)
/ NewStore returns a reference to a new Store object with the provided DB. The
/ store will be created with a PruneNothing pruning strategy by default. After
/ a store is created, KVStores must be mounted and finally LoadLatestVersion or
/ LoadVersion must be called.
func NewStore(db dbm.DB, logger log.Logger, metricGatherer metrics.StoreMetrics) *Store {
return &Store{
db: db,
logger: logger,
iavlCacheSize: iavl.DefaultIAVLCacheSize,
iavlDisableFastNode: iavlDisablefastNodeDefault,
storesParams: make(map[types.StoreKey]storeParams),
stores: make(map[types.StoreKey]types.CommitKVStore),
keysByName: make(map[string]types.StoreKey),
listeners: make(map[types.StoreKey]*types.MemoryListener),
removalMap: make(map[types.StoreKey]bool),
pruningManager: pruning.NewManager(db, logger),
metrics: metricGatherer,
}
}
/ GetPruning fetches the pruning strategy from the root store.
func (rs *Store)
GetPruning()
pruningtypes.PruningOptions {
return rs.pruningManager.GetOptions()
}
/ SetPruning sets the pruning strategy on the root store and all the sub-stores.
/ Note, calling SetPruning on the root store prior to LoadVersion or
/ LoadLatestVersion performs a no-op as the stores aren't mounted yet.
func (rs *Store)
SetPruning(pruningOpts pruningtypes.PruningOptions) {
rs.pruningManager.SetOptions(pruningOpts)
}
/ SetMetrics sets the metrics gatherer for the store package
func (rs *Store)
SetMetrics(metrics metrics.StoreMetrics) {
rs.metrics = metrics
}
/ SetSnapshotInterval sets the interval at which the snapshots are taken.
/ It is used by the store to determine which heights to retain until after the snapshot is complete.
func (rs *Store)
SetSnapshotInterval(snapshotInterval uint64) {
rs.pruningManager.SetSnapshotInterval(snapshotInterval)
}
func (rs *Store)
SetIAVLCacheSize(cacheSize int) {
rs.iavlCacheSize = cacheSize
}
func (rs *Store)
SetIAVLDisableFastNode(disableFastNode bool) {
rs.iavlDisableFastNode = disableFastNode
}
func (rs *Store)
SetIAVLSyncPruning(syncPruning bool) {
rs.iavlSyncPruning = syncPruning
}
/ GetStoreType implements Store.
func (rs *Store)
GetStoreType()
types.StoreType {
return types.StoreTypeMulti
}
/ MountStoreWithDB implements CommitMultiStore.
func (rs *Store)
MountStoreWithDB(key types.StoreKey, typ types.StoreType, db dbm.DB) {
if key == nil {
panic("MountIAVLStore()
key cannot be nil")
}
if _, ok := rs.storesParams[key]; ok {
panic(fmt.Sprintf("store duplicate store key %v", key))
}
if _, ok := rs.keysByName[key.Name()]; ok {
panic(fmt.Sprintf("store duplicate store key name %v", key))
}
rs.storesParams[key] = newStoreParams(key, db, typ, 0)
rs.keysByName[key.Name()] = key
}
/ GetCommitStore returns a mounted CommitStore for a given StoreKey. If the
/ store is wrapped in an inter-block cache, it will be unwrapped before returning.
func (rs *Store)
GetCommitStore(key types.StoreKey)
types.CommitStore {
return rs.GetCommitKVStore(key)
}
/ GetCommitKVStore returns a mounted CommitKVStore for a given StoreKey. If the
/ store is wrapped in an inter-block cache, it will be unwrapped before returning.
func (rs *Store)
GetCommitKVStore(key types.StoreKey)
types.CommitKVStore {
/ If the Store has an inter-block cache, first attempt to lookup and unwrap
/ the underlying CommitKVStore by StoreKey. If it does not exist, fallback to
/ the main mapping of CommitKVStores.
if rs.interBlockCache != nil {
if store := rs.interBlockCache.Unwrap(key); store != nil {
return store
}
}
return rs.stores[key]
}
/ StoreKeysByName returns mapping storeNames -> StoreKeys
func (rs *Store)
StoreKeysByName()
map[string]types.StoreKey {
return rs.keysByName
}
/ LoadLatestVersionAndUpgrade implements CommitMultiStore
func (rs *Store)
LoadLatestVersionAndUpgrade(upgrades *types.StoreUpgrades)
error {
ver := GetLatestVersion(rs.db)
return rs.loadVersion(ver, upgrades)
}
/ LoadVersionAndUpgrade allows us to rename substores while loading an older version
func (rs *Store)
LoadVersionAndUpgrade(ver int64, upgrades *types.StoreUpgrades)
error {
return rs.loadVersion(ver, upgrades)
}
/ LoadLatestVersion implements CommitMultiStore.
func (rs *Store)
LoadLatestVersion()
error {
ver := GetLatestVersion(rs.db)
return rs.loadVersion(ver, nil)
}
/ LoadVersion implements CommitMultiStore.
func (rs *Store)
LoadVersion(ver int64)
error {
return rs.loadVersion(ver, nil)
}
func (rs *Store)
loadVersion(ver int64, upgrades *types.StoreUpgrades)
error {
infos := make(map[string]types.StoreInfo)
rs.logger.Debug("loadVersion", "ver", ver)
cInfo := &types.CommitInfo{
}
/ load old data if we are not version 0
if ver != 0 {
var err error
cInfo, err = rs.GetCommitInfo(ver)
if err != nil {
return err
}
/ convert StoreInfos slice to map
for _, storeInfo := range cInfo.StoreInfos {
infos[storeInfo.Name] = storeInfo
}
}
/ load each Store (note this doesn't panic on unmounted keys now)
newStores := make(map[types.StoreKey]types.CommitKVStore)
storesKeys := make([]types.StoreKey, 0, len(rs.storesParams))
for key := range rs.storesParams {
storesKeys = append(storesKeys, key)
}
if upgrades != nil {
/ deterministic iteration order for upgrades
/ (as the underlying store may change and
/ upgrades make store changes where the execution order may matter)
sort.Slice(storesKeys, func(i, j int)
bool {
return storesKeys[i].Name() < storesKeys[j].Name()
})
}
for _, key := range storesKeys {
storeParams := rs.storesParams[key]
commitID := rs.getCommitID(infos, key.Name())
rs.logger.Debug("loadVersion commitID", "key", key, "ver", ver, "hash", fmt.Sprintf("%x", commitID.Hash))
/ If it has been added, set the initial version
if upgrades.IsAdded(key.Name()) || upgrades.RenamedFrom(key.Name()) != "" {
storeParams.initialVersion = uint64(ver) + 1
}
else if commitID.Version != ver && storeParams.typ == types.StoreTypeIAVL {
return fmt.Errorf("version of store %s mismatch root store's version; expected %d got %d; new stores should be added using StoreUpgrades", key.Name(), ver, commitID.Version)
}
store, err := rs.loadCommitStoreFromParams(key, commitID, storeParams)
if err != nil {
return errorsmod.Wrap(err, "failed to load store")
}
newStores[key] = store
/ If it was deleted, remove all data
if upgrades.IsDeleted(key.Name()) {
if err := deleteKVStore(store.(types.KVStore)); err != nil {
return errorsmod.Wrapf(err, "failed to delete store %s", key.Name())
}
rs.removalMap[key] = true
}
else if oldName := upgrades.RenamedFrom(key.Name()); oldName != "" {
/ handle renames specially
/ make an unregistered key to satisfy loadCommitStore params
oldKey := types.NewKVStoreKey(oldName)
oldParams := newStoreParams(oldKey, storeParams.db, storeParams.typ, 0)
/ load from the old name
oldStore, err := rs.loadCommitStoreFromParams(oldKey, rs.getCommitID(infos, oldName), oldParams)
if err != nil {
return errorsmod.Wrapf(err, "failed to load old store %s", oldName)
}
/ move all data
if err := moveKVStoreData(oldStore.(types.KVStore), store.(types.KVStore)); err != nil {
return errorsmod.Wrapf(err, "failed to move store %s -> %s", oldName, key.Name())
}
/ add the old key so its deletion is committed
newStores[oldKey] = oldStore
/ this will ensure it's not perpetually stored in commitInfo
rs.removalMap[oldKey] = true
}
}
rs.lastCommitInfo = cInfo
rs.stores = newStores
/ load any snapshot heights we missed from disk to be pruned on the next run
if err := rs.pruningManager.LoadSnapshotHeights(rs.db); err != nil {
return err
}
return nil
}
func (rs *Store)
getCommitID(infos map[string]types.StoreInfo, name string)
types.CommitID {
info, ok := infos[name]
if !ok {
return types.CommitID{
}
}
return info.CommitId
}
func deleteKVStore(kv types.KVStore)
error {
/ Note that we cannot write while iterating, so load all keys here, delete below
var keys [][]byte
itr := kv.Iterator(nil, nil)
for itr.Valid() {
keys = append(keys, itr.Key())
itr.Next()
}
if err := itr.Close(); err != nil {
return err
}
for _, k := range keys {
kv.Delete(k)
}
return nil
}
/ we simulate move by a copy and delete
func moveKVStoreData(oldDB, newDB types.KVStore)
error {
/ we read from one and write to another
itr := oldDB.Iterator(nil, nil)
for itr.Valid() {
newDB.Set(itr.Key(), itr.Value())
itr.Next()
}
if err := itr.Close(); err != nil {
return err
}
/ then delete the old store
return deleteKVStore(oldDB)
}
/ PruneSnapshotHeight prunes the given height according to the prune strategy.
/ If the strategy is PruneNothing, this is a no-op.
/ For other strategies, this height is persisted until the snapshot is operated.
func (rs *Store)
PruneSnapshotHeight(height int64) {
rs.pruningManager.HandleSnapshotHeight(height)
}
/ SetInterBlockCache sets the Store's internal inter-block (persistent)
cache.
/ When this is defined, all CommitKVStores will be wrapped with their respective
/ inter-block cache.
func (rs *Store)
SetInterBlockCache(c types.MultiStorePersistentCache) {
rs.interBlockCache = c
}
/ SetTracer sets the tracer for the MultiStore that the underlying
/ stores will utilize to trace operations. A MultiStore is returned.
func (rs *Store)
SetTracer(w io.Writer)
types.MultiStore {
rs.traceWriter = w
return rs
}
/ SetTracingContext updates the tracing context for the MultiStore by merging
/ the given context with the existing context by key. Any existing keys will
/ be overwritten. It is implied that the caller should update the context when
/ necessary between tracing operations. It returns a modified MultiStore.
func (rs *Store)
SetTracingContext(tc types.TraceContext)
types.MultiStore {
rs.traceContextMutex.Lock()
defer rs.traceContextMutex.Unlock()
rs.traceContext = rs.traceContext.Merge(tc)
return rs
}
func (rs *Store)
getTracingContext()
types.TraceContext {
rs.traceContextMutex.Lock()
defer rs.traceContextMutex.Unlock()
if rs.traceContext == nil {
return nil
}
ctx := types.TraceContext{
}
maps.Copy(ctx, rs.traceContext)
return ctx
}
/ TracingEnabled returns if tracing is enabled for the MultiStore.
func (rs *Store)
TracingEnabled()
bool {
return rs.traceWriter != nil
}
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
func (rs *Store)
AddListeners(keys []types.StoreKey) {
for i := range keys {
listener := rs.listeners[keys[i]]
if listener == nil {
rs.listeners[keys[i]] = types.NewMemoryListener()
}
}
}
/ ListeningEnabled returns if listening is enabled for a specific KVStore
func (rs *Store)
ListeningEnabled(key types.StoreKey)
bool {
if ls, ok := rs.listeners[key]; ok {
return ls != nil
}
return false
}
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
/ Calling PopStateCache destroys only the currently accumulated state in each listener
/ not the state in the store itself. This is a mutating and destructive operation.
/ This method has been synchronized.
func (rs *Store)
PopStateCache() []*types.StoreKVPair {
var cache []*types.StoreKVPair
for key := range rs.listeners {
ls := rs.listeners[key]
if ls != nil {
cache = append(cache, ls.PopStateCache()...)
}
}
sort.SliceStable(cache, func(i, j int)
bool {
return cache[i].StoreKey < cache[j].StoreKey
})
return cache
}
/ LatestVersion returns the latest version in the store
func (rs *Store)
LatestVersion()
int64 {
return rs.LastCommitID().Version
}
/ LastCommitID implements Committer/CommitStore.
func (rs *Store)
LastCommitID()
types.CommitID {
if rs.lastCommitInfo == nil {
emptyHash := sha256.Sum256([]byte{
})
appHash := emptyHash[:]
return types.CommitID{
Version: GetLatestVersion(rs.db),
Hash: appHash, / set empty apphash to sha256([]byte{
})
if info is nil
}
}
if len(rs.lastCommitInfo.CommitID().Hash) == 0 {
emptyHash := sha256.Sum256([]byte{
})
appHash := emptyHash[:]
return types.CommitID{
Version: rs.lastCommitInfo.Version,
Hash: appHash, / set empty apphash to sha256([]byte{
})
if hash is nil
}
}
return rs.lastCommitInfo.CommitID()
}
/ Commit implements Committer/CommitStore.
func (rs *Store)
Commit()
types.CommitID {
var previousHeight, version int64
if rs.lastCommitInfo.GetVersion() == 0 && rs.initialVersion > 1 {
/ This case means that no commit has been made in the store, we
/ start from initialVersion.
version = rs.initialVersion
}
else {
/ This case can means two things:
/ - either there was already a previous commit in the store, in which
/ case we increment the version from there,
/ - or there was no previous commit, and initial version was not set,
/ in which case we start at version 1.
previousHeight = rs.lastCommitInfo.GetVersion()
version = previousHeight + 1
}
if rs.commitHeader.Height != version {
rs.logger.Debug("commit header and version mismatch", "header_height", rs.commitHeader.Height, "version", version)
}
rs.lastCommitInfo = commitStores(version, rs.stores, rs.removalMap)
rs.lastCommitInfo.Timestamp = rs.commitHeader.Time
defer rs.flushMetadata(rs.db, version, rs.lastCommitInfo)
/ remove remnants of removed stores
for sk := range rs.removalMap {
if _, ok := rs.stores[sk]; ok {
delete(rs.stores, sk)
delete(rs.storesParams, sk)
delete(rs.keysByName, sk.Name())
}
}
/ reset the removalMap
rs.removalMap = make(map[types.StoreKey]bool)
if err := rs.handlePruning(version); err != nil {
rs.logger.Error(
"failed to prune store, please check your pruning configuration",
"err", err,
)
}
return types.CommitID{
Version: version,
Hash: rs.lastCommitInfo.Hash(),
}
}
/ WorkingHash returns the current hash of the store.
/ it will be used to get the current app hash before commit.
func (rs *Store)
WorkingHash() []byte {
storeInfos := make([]types.StoreInfo, 0, len(rs.stores))
storeKeys := keysFromStoreKeyMap(rs.stores)
for _, key := range storeKeys {
store := rs.stores[key]
if store.GetStoreType() != types.StoreTypeIAVL {
continue
}
if !rs.removalMap[key] {
si := types.StoreInfo{
Name: key.Name(),
CommitId: types.CommitID{
Hash: store.WorkingHash(),
},
}
storeInfos = append(storeInfos, si)
}
}
sort.SliceStable(storeInfos, func(i, j int)
bool {
return storeInfos[i].Name < storeInfos[j].Name
})
return types.CommitInfo{
StoreInfos: storeInfos
}.Hash()
}
/ CacheWrap implements CacheWrapper/Store/CommitStore.
func (rs *Store)
CacheWrap()
types.CacheWrap {
return rs.CacheMultiStore().(types.CacheWrap)
}
/ CacheWrapWithTrace implements the CacheWrapper interface.
func (rs *Store)
CacheWrapWithTrace(_ io.Writer, _ types.TraceContext)
types.CacheWrap {
return rs.CacheWrap()
}
/ 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)
for k, v := range rs.stores {
store := types.KVStore(v)
/ Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
/ set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(k) {
store = listenkv.NewStore(store, k, rs.listeners[k])
}
stores[k] = store
}
return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.getTracingContext())
}
/ CacheMultiStoreWithVersion is analogous to CacheMultiStore except that it
/ attempts to load stores at a given version (height). An error is returned if
/ any store cannot be loaded. This should only be used for querying and
/ iterating at past heights.
func (rs *Store)
CacheMultiStoreWithVersion(version int64) (types.CacheMultiStore, error) {
cachedStores := make(map[types.StoreKey]types.CacheWrapper)
var commitInfo *types.CommitInfo
storeInfos := map[string]bool{
}
for key, store := range rs.stores {
var cacheStore types.KVStore
switch store.GetStoreType() {
case types.StoreTypeIAVL:
/ If the store is wrapped with an inter-block cache, we must first unwrap
/ it to get the underlying IAVL store.
store = rs.GetCommitKVStore(key)
/ Attempt to lazy-load an already saved IAVL store version. If the
/ version does not exist or is pruned, an error should be returned.
var err error
cacheStore, err = store.(*iavl.Store).GetImmutable(version)
/ if we got error from loading a module store
/ we fetch commit info of this version
/ we use commit info to check if the store existed at this version or not
if err != nil {
if commitInfo == nil {
var errCommitInfo error
commitInfo, errCommitInfo = rs.GetCommitInfo(version)
if errCommitInfo != nil {
return nil, errCommitInfo
}
for _, storeInfo := range commitInfo.StoreInfos {
storeInfos[storeInfo.Name] = true
}
}
/ If the store existed at this version, it means there's actually an error
/ getting the root store at this version.
if storeInfos[key.Name()] {
return nil, err
}
}
default:
cacheStore = store
}
/ Wire the listenkv.Store to allow listeners to observe the writes from the cache store,
/ set same listeners on cache store will observe duplicated writes.
if rs.ListeningEnabled(key) {
cacheStore = listenkv.NewStore(cacheStore, key, rs.listeners[key])
}
cachedStores[key] = cacheStore
}
return cachemulti.NewStore(rs.db, cachedStores, rs.keysByName, rs.traceWriter, rs.getTracingContext()), nil
}
/ GetStore returns a mounted Store for a given StoreKey. If the StoreKey does
/ not exist, it will panic. If the Store is wrapped in an inter-block cache, it
/ will be unwrapped prior to being returned.
/
/ TODO: This isn't used directly upstream. Consider returning the Store as-is
/ instead of unwrapping.
func (rs *Store)
GetStore(key types.StoreKey)
types.Store {
store := rs.GetCommitKVStore(key)
if store == nil {
panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
}
return store
}
/ GetKVStore returns a mounted KVStore for a given StoreKey. If tracing is
/ enabled on the KVStore, a wrapped TraceKVStore will be returned with the root
/ store's tracer, otherwise, the original KVStore will be returned.
/
/ NOTE: The returned KVStore may be wrapped in an inter-block cache if it is
/ set on the root store.
func (rs *Store)
GetKVStore(key types.StoreKey)
types.KVStore {
s := rs.stores[key]
if s == nil {
panic(fmt.Sprintf("store does not exist for key: %s", key.Name()))
}
store := types.KVStore(s)
if rs.TracingEnabled() {
store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext())
}
if rs.ListeningEnabled(key) {
store = listenkv.NewStore(store, key, rs.listeners[key])
}
return store
}
func (rs *Store)
handlePruning(version int64)
error {
pruneHeight := rs.pruningManager.GetPruningHeight(version)
rs.logger.Debug("prune start", "height", version)
defer rs.logger.Debug("prune end", "height", version)
return rs.PruneStores(pruneHeight)
}
/ PruneStores prunes all history upto the specific height of the multi store.
func (rs *Store)
PruneStores(pruningHeight int64) (err error) {
if pruningHeight <= 0 {
rs.logger.Debug("pruning skipped, height is less than or equal to 0")
return nil
}
rs.logger.Debug("pruning store", "heights", pruningHeight)
for key, store := range rs.stores {
rs.logger.Debug("pruning store", "key", key) / Also log store.name (a private variable)?
/ If the store is wrapped with an inter-block cache, we must first unwrap
/ it to get the underlying IAVL store.
if store.GetStoreType() != types.StoreTypeIAVL {
continue
}
store = rs.GetCommitKVStore(key)
err := store.(*iavl.Store).DeleteVersionsTo(pruningHeight)
if err == nil {
continue
}
if errors.Is(err, iavltree.ErrVersionDoesNotExist) {
return err
}
rs.logger.Error("failed to prune store", "key", key, "err", err)
}
return nil
}
/ getStoreByName performs a lookup of a StoreKey given a store name typically
/ provided in a path. The StoreKey is then used to perform a lookup and return
/ a Store. If the Store is wrapped in an inter-block cache, it will be unwrapped
/ prior to being returned. If the StoreKey does not exist, nil is returned.
func (rs *Store)
GetStoreByName(name string)
types.Store {
key := rs.keysByName[name]
if key == nil {
return nil
}
return rs.GetCommitKVStore(key)
}
/ Query calls substore.Query with the same `req` where `req.Path` is
/ modified to remove the substore prefix.
/ Ie. `req.Path` here is `/<substore>/<path>`, and trimmed to `/<path>` for the substore.
/ TODO: add proof for `multistore -> substore`.
func (rs *Store)
Query(req *types.RequestQuery) (*types.ResponseQuery, error) {
path := req.Path
storeName, subpath, err := parsePath(path)
if err != nil {
return &types.ResponseQuery{
}, err
}
store := rs.GetStoreByName(storeName)
if store == nil {
return &types.ResponseQuery{
}, errorsmod.Wrapf(types.ErrUnknownRequest, "no such store: %s", storeName)
}
queryable, ok := store.(types.Queryable)
if !ok {
return &types.ResponseQuery{
}, errorsmod.Wrapf(types.ErrUnknownRequest, "store %s (type %T)
doesn't support queries", storeName, store)
}
/ trim the path and make the query
req.Path = subpath
res, err := queryable.Query(req)
if !req.Prove || !RequireProof(subpath) {
return res, err
}
if res.ProofOps == nil || len(res.ProofOps.Ops) == 0 {
return &types.ResponseQuery{
}, errorsmod.Wrap(types.ErrInvalidRequest, "proof is unexpectedly empty; ensure height has not been pruned")
}
/ If the request's height is the latest height we've committed, then utilize
/ the store's lastCommitInfo as this commit info may not be flushed to disk.
/ Otherwise, we query for the commit info from disk.
var commitInfo *types.CommitInfo
if res.Height == rs.lastCommitInfo.Version {
commitInfo = rs.lastCommitInfo
}
else {
commitInfo, err = rs.GetCommitInfo(res.Height)
if err != nil {
return &types.ResponseQuery{
}, err
}
}
/ Restore origin path and append proof op.
res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeName))
return res, nil
}
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
func (rs *Store)
SetInitialVersion(version int64)
error {
rs.initialVersion = version
/ Loop through all the stores, if it's an IAVL store, then set initial
/ version on it.
for key, store := range rs.stores {
if store.GetStoreType() == types.StoreTypeIAVL {
/ If the store is wrapped with an inter-block cache, we must first unwrap
/ it to get the underlying IAVL store.
store = rs.GetCommitKVStore(key)
store.(types.StoreWithInitialVersion).SetInitialVersion(version)
}
}
return nil
}
/ parsePath expects a format like /<storeName>[/<subpath>]
/ Must start with /, subpath may be empty
/ Returns error if it doesn't start with /
func parsePath(path string) (storeName, subpath string, err error) {
if !strings.HasPrefix(path, "/") {
return storeName, subpath, errorsmod.Wrapf(types.ErrUnknownRequest, "invalid path: %s", path)
}
storeName, subpath, found := strings.Cut(path[1:], "/")
if !found {
return storeName, subpath, nil
}
return storeName, "/" + subpath, nil
}
/---------------------- Snapshotting ------------------
/ Snapshot implements snapshottypes.Snapshotter. The snapshot output for a given format must be
/ identical across nodes such that chunks from different sources fit together. If the output for a
/ given format changes (at the byte level), the snapshot format must be bumped - see
/ TestMultistoreSnapshot_Checksum test.
func (rs *Store)
Snapshot(height uint64, protoWriter protoio.Writer)
error {
if height == 0 {
return errorsmod.Wrap(types.ErrLogic, "cannot snapshot height 0")
}
if height > uint64(GetLatestVersion(rs.db)) {
return errorsmod.Wrapf(types.ErrLogic, "cannot snapshot future height %v", height)
}
/ Collect stores to snapshot (only IAVL stores are supported)
type namedStore struct {
*iavl.Store
name string
}
stores := []namedStore{
}
keys := keysFromStoreKeyMap(rs.stores)
for _, key := range keys {
switch store := rs.GetCommitKVStore(key).(type) {
case *iavl.Store:
stores = append(stores, namedStore{
name: key.Name(),
Store: store
})
case *transient.Store, *mem.Store:
/ Non-persisted stores shouldn't be snapshotted
continue
default:
return errorsmod.Wrapf(types.ErrLogic,
"don't know how to snapshot store %q of type %T", key.Name(), store)
}
}
sort.Slice(stores, func(i, j int)
bool {
return strings.Compare(stores[i].name, stores[j].name) == -1
})
/ Export each IAVL store. Stores are serialized as a stream of SnapshotItem Protobuf
/ messages. The first item contains a SnapshotStore with store metadata (i.e. name),
/ and the following messages contain a SnapshotNode (i.e. an ExportNode). Store changes
/ are demarcated by new SnapshotStore items.
for _, store := range stores {
rs.logger.Debug("starting snapshot", "store", store.name, "height", height)
exporter, err := store.Export(int64(height))
if err != nil {
rs.logger.Error("snapshot failed; exporter error", "store", store.name, "err", err)
return err
}
err = func()
error {
defer exporter.Close()
err := protoWriter.WriteMsg(&snapshottypes.SnapshotItem{
Item: &snapshottypes.SnapshotItem_Store{
Store: &snapshottypes.SnapshotStoreItem{
Name: store.name,
},
},
})
if err != nil {
rs.logger.Error("snapshot failed; item store write failed", "store", store.name, "err", err)
return err
}
nodeCount := 0
for {
node, err := exporter.Next()
if errors.Is(err, iavltree.ErrorExportDone) {
rs.logger.Debug("snapshot Done", "store", store.name, "nodeCount", nodeCount)
break
}
else if err != nil {
return err
}
err = protoWriter.WriteMsg(&snapshottypes.SnapshotItem{
Item: &snapshottypes.SnapshotItem_IAVL{
IAVL: &snapshottypes.SnapshotIAVLItem{
Key: node.Key,
Value: node.Value,
Height: int32(node.Height),
Version: node.Version,
},
},
})
if err != nil {
return err
}
nodeCount++
}
return nil
}()
if err != nil {
return err
}
}
return nil
}
/ Restore implements snapshottypes.Snapshotter.
/ returns next snapshot item and error.
func (rs *Store)
Restore(
height uint64, format uint32, protoReader protoio.Reader,
) (snapshottypes.SnapshotItem, error) {
/ Import nodes into stores. The first item is expected to be a SnapshotItem containing
/ a SnapshotStoreItem, telling us which store to import into. The following items will contain
/ SnapshotNodeItem (i.e. ExportNode)
until we reach the next SnapshotStoreItem or EOF.
var importer *iavltree.Importer
var snapshotItem snapshottypes.SnapshotItem
loop:
for {
snapshotItem = snapshottypes.SnapshotItem{
}
err := protoReader.ReadMsg(&snapshotItem)
if errors.Is(err, io.EOF) {
break
}
else if err != nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(err, "invalid protobuf message")
}
switch item := snapshotItem.Item.(type) {
case *snapshottypes.SnapshotItem_Store:
if importer != nil {
err = importer.Commit()
if err != nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(err, "IAVL commit failed")
}
importer.Close()
}
store, ok := rs.GetStoreByName(item.Store.Name).(*iavl.Store)
if !ok || store == nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrapf(types.ErrLogic, "cannot import into non-IAVL store %q", item.Store.Name)
}
importer, err = store.Import(int64(height))
if err != nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(err, "import failed")
}
defer importer.Close()
/ Importer height must reflect the node height (which usually matches the block height, but not always)
rs.logger.Debug("restoring snapshot", "store", item.Store.Name)
case *snapshottypes.SnapshotItem_IAVL:
if importer == nil {
rs.logger.Error("failed to restore; received IAVL node item before store item")
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(types.ErrLogic, "received IAVL node item before store item")
}
if item.IAVL.Height > math.MaxInt8 {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrapf(types.ErrLogic, "node height %v cannot exceed %v",
item.IAVL.Height, math.MaxInt8)
}
node := &iavltree.ExportNode{
Key: item.IAVL.Key,
Value: item.IAVL.Value,
Height: int8(item.IAVL.Height),
Version: item.IAVL.Version,
}
/ Protobuf does not differentiate between []byte{
}
as nil, but fortunately IAVL does
/ not allow nil keys nor nil values for leaf nodes, so we can always set them to empty.
if node.Key == nil {
node.Key = []byte{
}
}
if node.Height == 0 && node.Value == nil {
node.Value = []byte{
}
}
err := importer.Add(node)
if err != nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(err, "IAVL node import failed")
}
default:
break loop
}
}
if importer != nil {
err := importer.Commit()
if err != nil {
return snapshottypes.SnapshotItem{
}, errorsmod.Wrap(err, "IAVL commit failed")
}
importer.Close()
}
rs.flushMetadata(rs.db, int64(height), rs.buildCommitInfo(int64(height)))
return snapshotItem, rs.LoadLatestVersion()
}
func (rs *Store)
loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) {
var db dbm.DB
if params.db != nil {
db = dbm.NewPrefixDB(params.db, []byte("s/_/"))
}
else {
prefix := "s/k:" + params.key.Name() + "/"
db = dbm.NewPrefixDB(rs.db, []byte(prefix))
}
switch params.typ {
case types.StoreTypeMulti:
panic("recursive MultiStores not yet supported")
case types.StoreTypeIAVL:
store, err := iavl.LoadStoreWithOpts(db, rs.logger, key, id, params.initialVersion, rs.iavlCacheSize, rs.iavlDisableFastNode, rs.metrics, iavltree.AsyncPruningOption(!rs.iavlSyncPruning))
if err != nil {
return nil, err
}
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
/ branched as that will create unexpected behavior.
store = rs.interBlockCache.GetStoreCache(key, store)
}
return store, err
case types.StoreTypeDB:
return commitDBStoreAdapter{
Store: dbadapter.Store{
DB: db
}}, nil
case types.StoreTypeTransient:
_, ok := key.(*types.TransientStoreKey)
if !ok {
return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String())
}
return transient.NewStore(), nil
case types.StoreTypeMemory:
if _, ok := key.(*types.MemoryStoreKey); !ok {
return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String())
}
return mem.NewStore(), nil
default:
panic(fmt.Sprintf("unrecognized store type %v", params.typ))
}
}
func (rs *Store)
buildCommitInfo(version int64) *types.CommitInfo {
keys := keysFromStoreKeyMap(rs.stores)
storeInfos := []types.StoreInfo{
}
for _, key := range keys {
store := rs.stores[key]
storeType := store.GetStoreType()
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory {
continue
}
storeInfos = append(storeInfos, types.StoreInfo{
Name: key.Name(),
CommitId: store.LastCommitID(),
})
}
return &types.CommitInfo{
Version: version,
StoreInfos: storeInfos,
}
}
/ RollbackToVersion delete the versions after `target` and update the latest version.
func (rs *Store)
RollbackToVersion(target int64)
error {
if target <= 0 {
return fmt.Errorf("invalid rollback height target: %d", target)
}
for key, store := range rs.stores {
if store.GetStoreType() == types.StoreTypeIAVL {
/ If the store is wrapped with an inter-block cache, we must first unwrap
/ it to get the underlying IAVL store.
store = rs.GetCommitKVStore(key)
err := store.(*iavl.Store).LoadVersionForOverwriting(target)
if err != nil {
return err
}
}
}
rs.flushMetadata(rs.db, target, rs.buildCommitInfo(target))
return rs.LoadLatestVersion()
}
/ SetCommitHeader sets the commit block header of the store.
func (rs *Store)
SetCommitHeader(h cmtproto.Header) {
rs.commitHeader = h
}
/ GetCommitInfo attempts to retrieve CommitInfo for a given version/height. It
/ will return an error if no CommitInfo exists, we fail to unmarshal the record
/ or if we cannot retrieve the object from the DB.
func (rs *Store)
GetCommitInfo(ver int64) (*types.CommitInfo, error) {
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver)
bz, err := rs.db.Get([]byte(cInfoKey))
if err != nil {
return nil, errorsmod.Wrap(err, "failed to get commit info")
}
else if bz == nil {
return nil, errors.New("no commit info found")
}
cInfo := &types.CommitInfo{
}
if err = cInfo.Unmarshal(bz); err != nil {
return nil, errorsmod.Wrap(err, "failed unmarshal commit info")
}
return cInfo, nil
}
func (rs *Store)
flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo) {
rs.logger.Debug("flushing metadata", "height", version)
batch := db.NewBatch()
defer func() {
_ = batch.Close()
}()
if cInfo != nil {
flushCommitInfo(batch, version, cInfo)
}
else {
rs.logger.Debug("commitInfo is nil, not flushed", "height", version)
}
flushLatestVersion(batch, version)
if err := batch.WriteSync(); err != nil {
panic(fmt.Errorf("error on batch write %w", err))
}
rs.logger.Debug("flushing metadata finished", "height", version)
}
type storeParams struct {
key types.StoreKey
db dbm.DB
typ types.StoreType
initialVersion uint64
}
func newStoreParams(key types.StoreKey, db dbm.DB, typ types.StoreType, initialVersion uint64)
storeParams {
return storeParams{
key: key,
db: db,
typ: typ,
initialVersion: initialVersion,
}
}
func GetLatestVersion(db dbm.DB)
int64 {
bz, err := db.Get([]byte(latestVersionKey))
if err != nil {
panic(err)
}
else if bz == nil {
return 0
}
var latestVersion int64
if err := gogotypes.StdInt64Unmarshal(&latestVersion, bz); err != nil {
panic(err)
}
return latestVersion
}
/ Commits each store and returns a new commitInfo.
func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore, removalMap map[types.StoreKey]bool) *types.CommitInfo {
storeInfos := make([]types.StoreInfo, 0, len(storeMap))
storeKeys := keysFromStoreKeyMap(storeMap)
for _, key := range storeKeys {
store := storeMap[key]
last := store.LastCommitID()
/ If a commit event execution is interrupted, a new iavl store's version
/ will be larger than the RMS's metadata, when the block is replayed, we
/ should avoid committing that iavl store again.
var commitID types.CommitID
if last.Version >= version {
last.Version = version
commitID = last
}
else {
commitID = store.Commit()
}
storeType := store.GetStoreType()
if storeType == types.StoreTypeTransient || storeType == types.StoreTypeMemory {
continue
}
if !removalMap[key] {
si := types.StoreInfo{
}
si.Name = key.Name()
si.CommitId = commitID
storeInfos = append(storeInfos, si)
}
}
sort.SliceStable(storeInfos, func(i, j int)
bool {
return strings.Compare(storeInfos[i].Name, storeInfos[j].Name) < 0
})
return &types.CommitInfo{
Version: version,
StoreInfos: storeInfos,
}
}
func flushCommitInfo(batch dbm.Batch, version int64, cInfo *types.CommitInfo) {
bz, err := cInfo.Marshal()
if err != nil {
panic(err)
}
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version)
err = batch.Set([]byte(cInfoKey), bz)
if err != nil {
panic(err)
}
}
func flushLatestVersion(batch dbm.Batch, version int64) {
bz, err := gogotypes.StdInt64Marshal(version)
if err != nil {
panic(err)
}
err = batch.Set([]byte(latestVersionKey), bz)
if err != nil {
panic(err)
}
}
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 therootMulti.Store
needs to be branched, a cachemulti.Store
is used.
Copy
Ask AI
package cachemulti
import (
"fmt"
"io"
"maps"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/dbadapter"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
/ storeNameCtxKey is the TraceContext metadata key that identifies
/ the store which emitted a given trace.
const storeNameCtxKey = "store_name"
/----------------------------------------
/ 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
}
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 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,
)
Store {
cms := Store{
db: cachekv.NewStore(store),
stores: make(map[types.StoreKey]types.CacheWrap, len(stores)),
keys: keys,
traceWriter: traceWriter,
traceContext: traceContext,
}
for key, store := range stores {
if cms.TracingEnabled() {
tctx := cms.traceContext.Clone().Merge(types.TraceContext{
storeNameCtxKey: key.Name(),
})
store = tracekv.NewStore(store.(types.KVStore), cms.traceWriter, tctx)
}
cms.stores[key] = cachekv.NewStore(store.(types.KVStore))
}
return cms
}
/ NewStore creates a new Store object from a mapping of store keys to
/ 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,
)
Store {
return NewFromKVStore(dbadapter.Store{
DB: db
}, stores, keys, traceWriter, traceContext)
}
func newCacheMultiStoreFromCMS(cms Store)
Store {
stores := make(map[types.StoreKey]types.CacheWrapper)
for k, v := range cms.stores {
stores[k] = v
}
return NewFromKVStore(cms.db, stores, nil, cms.traceWriter, cms.traceContext)
}
/ SetTracer sets the tracer for the MultiStore that the underlying
/ stores will utilize to trace operations. A MultiStore is returned.
func (cms Store)
SetTracer(w io.Writer)
types.MultiStore {
cms.traceWriter = w
return cms
}
/ SetTracingContext updates the tracing context for the MultiStore by merging
/ the given context with the existing context by key. Any existing keys will
/ be overwritten. It is implied that the caller should update the context when
/ necessary between tracing operations. It returns a modified MultiStore.
func (cms Store)
SetTracingContext(tc types.TraceContext)
types.MultiStore {
if cms.traceContext != nil {
maps.Copy(cms.traceContext, tc)
}
else {
cms.traceContext = tc
}
return cms
}
/ TracingEnabled returns if tracing is enabled for the MultiStore.
func (cms Store)
TracingEnabled()
bool {
return cms.traceWriter != nil
}
/ LatestVersion returns the branch version of the store
func (cms Store)
LatestVersion()
int64 {
panic("cannot get latest version from branch cached multi-store")
}
/ GetStoreType returns the type of the store.
func (cms Store)
GetStoreType()
types.StoreType {
return types.StoreTypeMulti
}
/ Write calls Write on each underlying store.
func (cms Store)
Write() {
cms.db.Write()
for _, store := range cms.stores {
store.Write()
}
}
/ Implements CacheWrapper.
func (cms Store)
CacheWrap()
types.CacheWrap {
return cms.CacheMultiStore().(types.CacheWrap)
}
/ CacheWrapWithTrace implements the CacheWrapper interface.
func (cms Store)
CacheWrapWithTrace(_ io.Writer, _ types.TraceContext)
types.CacheWrap {
return cms.CacheWrap()
}
/ Implements MultiStore.
func (cms Store)
CacheMultiStore()
types.CacheMultiStore {
return newCacheMultiStoreFromCMS(cms)
}
/ CacheMultiStoreWithVersion implements the MultiStore interface. It will panic
/ as an already cached multi-store cannot load previous versions.
/
/ 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 branch cached multi-store with a version")
}
/ GetStore returns an underlying Store by key.
func (cms Store)
GetStore(key types.StoreKey)
types.Store {
s := cms.stores[key]
if key == nil || s == nil {
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
}
return s.(types.Store)
}
/ GetKVStore returns an underlying KVStore by key.
func (cms Store)
GetKVStore(key types.StoreKey)
types.KVStore {
store := cms.stores[key]
if key == nil || store == nil {
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
}
return store.(types.KVStore)
}
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 CommitKVStore
s. The KVStore
interface is primarily used to restrict modules from accessing the committer.
Individual KVStore
s 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.
CommitKVStore
s 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
Ask AI
package types
import (
"fmt"
"io"
"maps"
"slices"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
snapshottypes "cosmossdk.io/store/snapshots/types"
)
type Store interface {
GetStoreType()
StoreType
CacheWrapper
}
/ something that can persist to disk
type Committer interface {
Commit()
CommitID
LastCommitID()
CommitID
/ WorkingHash returns the hash of the KVStore's state before commit.
WorkingHash() []byte
SetPruning(pruningtypes.PruningOptions)
GetPruning()
pruningtypes.PruningOptions
}
/ Stores of MultiStore must implement CommitStore.
type CommitStore interface {
Committer
Store
}
/ Queryable allows a Store to expose internal state to the abci.Query
/ interface. Multistore can route requests to the proper Store.
/
/ This is an optional, but useful extension to any CommitStore
type Queryable interface {
Query(*RequestQuery) (*ResponseQuery, error)
}
type RequestQuery struct {
Data []byte
Path string
Height int64
Prove bool
}
type ResponseQuery struct {
Code uint32
Log string
Info string
Index int64
Key []byte
Value []byte
ProofOps *crypto.ProofOps
Height int64
Codespace string
}
/----------------------------------------
/ MultiStore
/ StoreUpgrades defines a series of transformations to apply the multistore db upon load
type StoreUpgrades struct {
Added []string `json:"added"`
Renamed []StoreRename `json:"renamed"`
Deleted []string `json:"deleted"`
}
/ StoreRename defines a name change of a sub-store.
/ All data previously under a PrefixStore with OldKey will be copied
/ to a PrefixStore with NewKey, then deleted from OldKey store.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}
/ IsAdded returns true if the given key should be added
func (s *StoreUpgrades)
IsAdded(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Added, key)
}
/ IsDeleted returns true if the given key should be deleted
func (s *StoreUpgrades)
IsDeleted(key string)
bool {
if s == nil {
return false
}
return slices.Contains(s.Deleted, key)
}
/ RenamedFrom returns the oldKey if it was renamed
/ Returns "" if it was not renamed
func (s *StoreUpgrades)
RenamedFrom(key string)
string {
if s == nil {
return ""
}
for _, re := range s.Renamed {
if re.NewKey == key {
return re.OldKey
}
}
return ""
}
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
/ LatestVersion returns the latest version in the store
LatestVersion()
int64
}
/ From MultiStore.CacheMultiStore()....
type CacheMultiStore interface {
MultiStore
Write() / Writes operations to underlying KVStore
}
/ 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)
/ SetIAVLDisableFastNode enables/disables fastnode feature on iavl.
SetIAVLDisableFastNode(disable bool)
/ SetIAVLSyncPruning set sync/async pruning on iavl.
/ It is not recommended to use this option.
/ It is here to enable the prune command to force this to true, allowing the command to wait
/ for the pruning to finish before returning.
SetIAVLSyncPruning(sync bool)
/ RollbackToVersion rollback the db to specific version(height).
RollbackToVersion(version int64)
error
/ ListeningEnabled returns if listening is enabled for the KVStore belonging the provided StoreKey
ListeningEnabled(key StoreKey)
bool
/ AddListeners adds a listener for the KVStore belonging to the provided StoreKey
AddListeners(keys []StoreKey)
/ PopStateCache returns the accumulated state change messages from the CommitMultiStore
PopStateCache() []*StoreKVPair
/ SetMetrics sets the metrics for the KVStore
SetMetrics(metrics metrics.StoreMetrics)
}
/---------subsp-------------------------------
/ KVStore
/ 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
}
/ Iterator is an alias db's Iterator for convenience.
type Iterator = dbm.Iterator
/ 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
/ Writes operations to underlying KVStore
Write()
}
/ CommitKVStore is an interface for MultiStore.
type CommitKVStore interface {
Committer
KVStore
}
/----------------------------------------
/ 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
}
type CacheWrapper interface {
/ CacheWrap branches a store.
CacheWrap()
CacheWrap
/ CacheWrapWithTrace branches a store with tracing enabled.
CacheWrapWithTrace(w io.Writer, tc TraceContext)
CacheWrap
}
func (cid CommitID)
IsZero()
bool {
return cid.Version == 0 && len(cid.Hash) == 0
}
func (cid CommitID)
String()
string {
return fmt.Sprintf("CommitID{%v:%X
}", cid.Hash, cid.Version)
}
/----------------------------------------
/ Store types
/ kind of store
type StoreType int
const (
StoreTypeMulti StoreType = iota
StoreTypeDB
StoreTypeIAVL
StoreTypeTransient
StoreTypeMemory
StoreTypeSMT
StoreTypePersistent
)
func (st StoreType)
String()
string {
switch st {
case StoreTypeMulti:
return "StoreTypeMulti"
case StoreTypeDB:
return "StoreTypeDB"
case StoreTypeIAVL:
return "StoreTypeIAVL"
case StoreTypeTransient:
return "StoreTypeTransient"
case StoreTypeMemory:
return "StoreTypeMemory"
case StoreTypeSMT:
return "StoreTypeSMT"
case StoreTypePersistent:
return "StoreTypePersistent"
}
return "unknown store type"
}
/----------------------------------------
/ Keys for accessing substores
/ StoreKey is a key used to index stores in a MultiStore.
type StoreKey interface {
Name()
string
String()
string
}
/ CapabilityKey represent the Cosmos SDK keys for object-capability
/ generation in the IBC protocol as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#data-structures
type CapabilityKey StoreKey
/ KVStoreKey is used for accessing substores.
/ Only the pointer value should ever be used - it functions as a capabilities key.
type KVStoreKey struct {
name string
}
/ NewKVStoreKey returns a new pointer to a KVStoreKey.
/ Use a pointer so keys don't collide.
func NewKVStoreKey(name string) *KVStoreKey {
if name == "" {
panic("empty key name not allowed")
}
return &KVStoreKey{
name: name,
}
}
/ NewKVStoreKeys returns a map of new pointers to KVStoreKey's.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewKVStoreKeys(names ...string)
map[string]*KVStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*KVStoreKey, len(names))
for _, n := range names {
keys[n] = NewKVStoreKey(n)
}
return keys
}
func (key *KVStoreKey)
Name()
string {
return key.name
}
func (key *KVStoreKey)
String()
string {
return fmt.Sprintf("KVStoreKey{%p, %s
}", key, key.name)
}
/ TransientStoreKey is used for indexing transient stores in a MultiStore
type TransientStoreKey struct {
name string
}
/ Constructs new TransientStoreKey
/ Must return a pointer according to the ocap principle
func NewTransientStoreKey(name string) *TransientStoreKey {
return &TransientStoreKey{
name: name,
}
}
/ Implements StoreKey
func (key *TransientStoreKey)
Name()
string {
return key.name
}
/ Implements StoreKey
func (key *TransientStoreKey)
String()
string {
return fmt.Sprintf("TransientStoreKey{%p, %s
}", key, key.name)
}
/ MemoryStoreKey defines a typed key to be used with an in-memory KVStore.
type MemoryStoreKey struct {
name string
}
func NewMemoryStoreKey(name string) *MemoryStoreKey {
return &MemoryStoreKey{
name: name
}
}
/ Name returns the name of the MemoryStoreKey.
func (key *MemoryStoreKey)
Name()
string {
return key.name
}
/ String returns a stringified representation of the MemoryStoreKey.
func (key *MemoryStoreKey)
String()
string {
return fmt.Sprintf("MemoryStoreKey{%p, %s
}", key, key.name)
}
/----------------------------------------
/ TraceContext contains TraceKVStore context data. It will be written with
/ every trace operation.
type TraceContext map[string]interface{
}
/ Clone clones tc into another instance of TraceContext.
func (tc TraceContext)
Clone()
TraceContext {
ret := TraceContext{
}
maps.Copy(ret, tc)
return ret
}
/ Merge merges value of newTc into tc.
func (tc TraceContext)
Merge(newTc TraceContext)
TraceContext {
if tc == nil {
tc = TraceContext{
}
}
maps.Copy(tc, newTc)
return tc
}
/ MultiStorePersistentCache defines an interface which provides inter-block
/ (persistent)
caching capabilities for multiple CommitKVStores based on StoreKeys.
type MultiStorePersistentCache interface {
/ Wrap and return the provided CommitKVStore with an inter-block (persistent)
/ cache.
GetStoreCache(key StoreKey, store CommitKVStore)
CommitKVStore
/ Return the underlying CommitKVStore for a StoreKey.
Unwrap(key StoreKey)
CommitKVStore
/ Reset the entire set of internal caches.
Reset()
}
/ StoreWithInitialVersion is a store that can have an arbitrary initial
/ version.
type StoreWithInitialVersion interface {
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
SetInitialVersion(version int64)
}
/ NewTransientStoreKeys constructs a new map of TransientStoreKey's
/ Must return pointers according to the ocap principle
/ The function will panic if there is a potential conflict in names
/ see `assertNoCommonPrefix` function for more details.
func NewTransientStoreKeys(names ...string)
map[string]*TransientStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*TransientStoreKey)
for _, n := range names {
keys[n] = NewTransientStoreKey(n)
}
return keys
}
/ NewMemoryStoreKeys constructs a new map matching store key names to their
/ respective MemoryStoreKey references.
/ The function will panic if there is a potential conflict in names (see `assertNoPrefix`
/ function for more details).
func NewMemoryStoreKeys(names ...string)
map[string]*MemoryStoreKey {
assertNoCommonPrefix(names)
keys := make(map[string]*MemoryStoreKey)
for _, n := range names {
keys[n] = NewMemoryStoreKey(n)
}
return keys
}
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
Ask AI
package keeper
import (
"context"
"fmt"
"cosmossdk.io/collections"
"cosmossdk.io/collections/indexes"
"cosmossdk.io/core/store"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
var _ ViewKeeper = (*BaseViewKeeper)(nil)
/ ViewKeeper defines a module interface that facilitates read only access to
/ account balances.
type ViewKeeper interface {
ValidateBalance(ctx context.Context, addr sdk.AccAddress)
error
HasBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin)
bool
GetAllBalances(ctx context.Context, addr sdk.AccAddress)
sdk.Coins
GetAccountsBalances(ctx context.Context) []types.Balance
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string)
sdk.Coin
LockedCoins(ctx context.Context, addr sdk.AccAddress)
sdk.Coins
SpendableCoins(ctx context.Context, addr sdk.AccAddress)
sdk.Coins
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string)
sdk.Coin
IterateAccountBalances(ctx context.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool))
IterateAllBalances(ctx context.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool))
}
func newBalancesIndexes(sb *collections.SchemaBuilder)
BalancesIndexes {
return BalancesIndexes{
Denom: indexes.NewReversePair[math.Int](/docs/sdk/next/documentation/state-storage/
sb, types.DenomAddressPrefix, "address_by_denom_index",
collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), collections.StringKey), / nolint:staticcheck / Note: refer to the LengthPrefixedAddressKey docs to understand why we do this.
indexes.WithReversePairUncheckedValue(), / denom to address indexes were stored as Key: Join(denom, address)
Value: []byte{0
}, this will migrate the value to []byte{
}
in a lazy way.
),
}
}
type BalancesIndexes struct {
Denom *indexes.ReversePair[sdk.AccAddress, string, math.Int]
}
func (b BalancesIndexes)
IndexesList() []collections.Index[collections.Pair[sdk.AccAddress, string], math.Int] {
return []collections.Index[collections.Pair[sdk.AccAddress, string], math.Int]{
b.Denom
}
}
/ BaseViewKeeper implements a read only keeper implementation of ViewKeeper.
type BaseViewKeeper struct {
cdc codec.BinaryCodec
storeService store.KVStoreService
ak types.AccountKeeper
logger log.Logger
Schema collections.Schema
Supply collections.Map[string, math.Int]
DenomMetadata collections.Map[string, types.Metadata]
SendEnabled collections.Map[string, bool]
Balances *collections.IndexedMap[collections.Pair[sdk.AccAddress, string], math.Int, BalancesIndexes]
Params collections.Item[types.Params]
}
/ NewBaseViewKeeper returns a new BaseViewKeeper.
func NewBaseViewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, ak types.AccountKeeper, logger log.Logger)
BaseViewKeeper {
sb := collections.NewSchemaBuilder(storeService)
k := BaseViewKeeper{
cdc: cdc,
storeService: storeService,
ak: ak,
logger: logger,
Supply: collections.NewMap(sb, types.SupplyKey, "supply", collections.StringKey, sdk.IntValue),
DenomMetadata: collections.NewMap(sb, types.DenomMetadataPrefix, "denom_metadata", collections.StringKey, codec.CollValue[types.Metadata](/docs/sdk/next/documentation/state-storage/cdc)),
SendEnabled: collections.NewMap(sb, types.SendEnabledPrefix, "send_enabled", collections.StringKey, codec.BoolValue), / NOTE: we use a bool value which uses protobuf to retain state backwards compat
Balances: collections.NewIndexedMap(sb, types.BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), types.BalanceValueCodec, newBalancesIndexes(sb)),
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](/docs/sdk/next/documentation/state-storage/cdc)),
}
schema, err := sb.Build()
if err != nil {
panic(err)
}
k.Schema = schema
return k
}
/ HasBalance returns whether or not an account has at least amt balance.
func (k BaseViewKeeper)
HasBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin)
bool {
return k.GetBalance(ctx, addr, amt.Denom).IsGTE(amt)
}
/ Logger returns a module-specific logger.
func (k BaseViewKeeper)
Logger()
log.Logger {
return k.logger
}
/ GetAllBalances returns all the account balances for the given account address.
func (k BaseViewKeeper)
GetAllBalances(ctx context.Context, addr sdk.AccAddress)
sdk.Coins {
balances := sdk.NewCoins()
k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin)
bool {
balances = balances.Add(balance)
return false
})
return balances.Sort()
}
/ GetAccountsBalances returns all the accounts balances from the store.
func (k BaseViewKeeper)
GetAccountsBalances(ctx context.Context) []types.Balance {
balances := make([]types.Balance, 0)
mapAddressToBalancesIdx := make(map[string]int)
k.IterateAllBalances(ctx, func(addr sdk.AccAddress, balance sdk.Coin)
bool {
idx, ok := mapAddressToBalancesIdx[addr.String()]
if ok {
/ address is already on the set of accounts balances
balances[idx].Coins = balances[idx].Coins.Add(balance)
balances[idx].Coins.Sort()
return false
}
accountBalance := types.Balance{
Address: addr.String(),
Coins: sdk.NewCoins(balance),
}
balances = append(balances, accountBalance)
mapAddressToBalancesIdx[addr.String()] = len(balances) - 1
return false
})
return balances
}
/ GetBalance returns the balance of a specific denomination for a given account
/ by address.
func (k BaseViewKeeper)
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string)
sdk.Coin {
amt, err := k.Balances.Get(ctx, collections.Join(addr, denom))
if err != nil {
return sdk.NewCoin(denom, math.ZeroInt())
}
return sdk.NewCoin(denom, amt)
}
/ 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 context.Context, addr sdk.AccAddress, cb func(sdk.Coin)
bool) {
err := k.Balances.Walk(ctx, collections.NewPrefixedPairRange[sdk.AccAddress, string](/docs/sdk/next/documentation/state-storage/addr), func(key collections.Pair[sdk.AccAddress, string], value math.Int) (stop bool, err error) {
return cb(sdk.NewCoin(key.K2(), value)), nil
})
if err != nil {
panic(err)
}
}
/ IterateAllBalances iterates over all the balances of all accounts and
/ denominations that are provided to a callback. If true is returned from the
/ callback, iteration is halted.
func (k BaseViewKeeper)
IterateAllBalances(ctx context.Context, cb func(sdk.AccAddress, sdk.Coin)
bool) {
err := k.Balances.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, string], value math.Int) (stop bool, err error) {
return cb(key.K1(), sdk.NewCoin(key.K2(), value)), nil
})
if err != nil {
panic(err)
}
}
/ LockedCoins returns all the coins that are not spendable (i.e. locked)
for an
/ account by address. For standard accounts, the result will always be no coins.
/ For vesting accounts, LockedCoins is delegated to the concrete vesting account
/ type.
func (k BaseViewKeeper)
LockedCoins(ctx context.Context, addr sdk.AccAddress)
sdk.Coins {
acc := k.ak.GetAccount(ctx, addr)
if acc != nil {
vacc, ok := acc.(types.VestingAccount)
if ok {
sdkCtx := sdk.UnwrapSDKContext(ctx)
return vacc.LockedCoins(sdkCtx.BlockTime())
}
}
return sdk.NewCoins()
}
/ SpendableCoins returns the total balances of spendable coins for an account
/ by address. If the account has no spendable coins, an empty Coins slice is
/ returned.
func (k BaseViewKeeper)
SpendableCoins(ctx context.Context, addr sdk.AccAddress)
sdk.Coins {
spendable, _ := k.spendableCoins(ctx, addr)
return spendable
}
/ SpendableCoin returns the balance of specific denomination of spendable coins
/ for an account by address. If the account has no spendable coin, a zero Coin
/ is returned.
func (k BaseViewKeeper)
SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string)
sdk.Coin {
balance := k.GetBalance(ctx, addr, denom)
locked := k.LockedCoins(ctx, addr)
return balance.SubAmount(locked.AmountOf(denom))
}
/ spendableCoins returns the coins the given address can spend alongside the total amount of coins it holds.
/ It exists for gas efficiency, in order to avoid to have to get balance multiple times.
func (k BaseViewKeeper)
spendableCoins(ctx context.Context, addr sdk.AccAddress) (spendable, total sdk.Coins) {
total = k.GetAllBalances(ctx, addr)
locked := k.LockedCoins(ctx, addr)
spendable, hasNeg := total.SafeSub(locked...)
if hasNeg {
spendable = sdk.NewCoins()
return
}
return
}
/ ValidateBalance validates all balances for a given account address returning
/ an error if any balance is invalid. It will check for vesting account types
/ and validate the balances against the original vesting balances.
/
/ CONTRACT: ValidateBalance should only be called upon genesis state. In the
/ case of vesting accounts, balances may change in a valid manner that would
/ otherwise yield an error from this call.
func (k BaseViewKeeper)
ValidateBalance(ctx context.Context, addr sdk.AccAddress)
error {
acc := k.ak.GetAccount(ctx, addr)
if acc == nil {
return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
}
balances := k.GetAllBalances(ctx, addr)
if !balances.IsValid() {
return fmt.Errorf("account balance of %s is invalid", balances)
}
vacc, ok := acc.(types.VestingAccount)
if ok {
ogv := vacc.GetOriginalVesting()
if ogv.IsAnyGT(balances) {
return fmt.Errorf("vesting amount %s cannot be greater than total amount %s", ogv, balances)
}
}
return nil
}
IAVL
Store
The default implementation of KVStore
and CommitKVStore
used in baseapp
is the iavl.Store
.
Copy
Ask AI
package iavl
import (
"errors"
"fmt"
"io"
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/iavl"
ics23 "github.com/cosmos/ics23/go"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/internal/kv"
"cosmossdk.io/store/metrics"
pruningtypes "cosmossdk.io/store/pruning/types"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
"cosmossdk.io/store/wrapper"
)
const (
DefaultIAVLCacheSize = 500000
)
var (
_ types.KVStore = (*Store)(nil)
_ types.CommitStore = (*Store)(nil)
_ types.CommitKVStore = (*Store)(nil)
_ types.Queryable = (*Store)(nil)
_ types.StoreWithInitialVersion = (*Store)(nil)
)
/ Store Implements types.KVStore and CommitKVStore.
type Store struct {
tree Tree
logger log.Logger
metrics metrics.StoreMetrics
}
/ LoadStore returns an IAVL Store as a CommitKVStore. Internally, it will load the
/ store's version (id)
from the provided DB. An error is returned if the version
/ fails to load, or if called with a positive version on an empty tree.
func LoadStore(db dbm.DB, logger log.Logger, key types.StoreKey, id types.CommitID, cacheSize int, disableFastNode bool, metrics metrics.StoreMetrics) (types.CommitKVStore, error) {
return LoadStoreWithInitialVersion(db, logger, key, id, 0, cacheSize, disableFastNode, metrics)
}
/ LoadStoreWithInitialVersion returns an IAVL Store as a CommitKVStore setting its initialVersion
/ to the one given. Internally, it will load the store's version (id)
from the
/ provided DB. An error is returned if the version fails to load, or if called with a positive
/ version on an empty tree.
func LoadStoreWithInitialVersion(db dbm.DB, logger log.Logger, key types.StoreKey, id types.CommitID, initialVersion uint64, cacheSize int, disableFastNode bool, metrics metrics.StoreMetrics) (types.CommitKVStore, error) {
return LoadStoreWithOpts(db, logger, key, id, initialVersion, cacheSize, disableFastNode, metrics, iavl.AsyncPruningOption(true))
}
func LoadStoreWithOpts(db dbm.DB, logger log.Logger, key types.StoreKey, id types.CommitID, initialVersion uint64, cacheSize int, disableFastNode bool, metrics metrics.StoreMetrics, opts ...iavl.Option) (types.CommitKVStore, error) {
/ store/v1 and app/v1 flows never require an initial version of 0
if initialVersion == 0 {
initialVersion = 1
}
opts = append(opts, iavl.InitialVersionOption(initialVersion))
tree := iavl.NewMutableTree(wrapper.NewDBWrapper(db), cacheSize, disableFastNode, logger, opts...)
isUpgradeable, err := tree.IsUpgradeable()
if err != nil {
return nil, err
}
if isUpgradeable && logger != nil {
logger.Info(
"Upgrading IAVL storage for faster queries + execution on live state. This may take a while",
"store_key", key.String(),
"version", initialVersion,
"commit", fmt.Sprintf("%X", id),
)
}
_, err = tree.LoadVersion(id.Version)
if err != nil {
return nil, err
}
if logger != nil {
logger.Debug("Finished loading IAVL tree")
}
return &Store{
tree: tree,
logger: logger,
metrics: metrics,
}, nil
}
/ UnsafeNewStore returns a reference to a new IAVL Store with a given mutable
/ IAVL tree reference. It should only be used for testing purposes.
/
/ CONTRACT: The IAVL tree should be fully loaded.
/ CONTRACT: PruningOptions passed in as argument must be the same as pruning options
/ passed into iavl.MutableTree
func UnsafeNewStore(tree *iavl.MutableTree) *Store {
return &Store{
tree: tree,
metrics: metrics.NewNoOpMetrics(),
}
}
/ GetImmutable returns a reference to a new store backed by an immutable IAVL
/ tree at a specific version (height)
without any pruning options. This should
/ be used for querying and iteration only. If the version does not exist or has
/ been pruned, an empty immutable IAVL tree will be used.
/ Any mutable operations executed will result in a panic.
func (st *Store)
GetImmutable(version int64) (*Store, error) {
if !st.VersionExists(version) {
return nil, errors.New("version mismatch on immutable IAVL tree; version does not exist. Version has either been pruned, or is for a future block height")
}
iTree, err := st.tree.GetImmutable(version)
if err != nil {
return nil, err
}
return &Store{
tree: &immutableTree{
iTree
},
metrics: st.metrics,
}, nil
}
/ Commit commits the current store state and returns a CommitID with the new
/ version and hash.
func (st *Store)
Commit()
types.CommitID {
defer st.metrics.MeasureSince("store", "iavl", "commit")
hash, version, err := st.tree.SaveVersion()
if err != nil {
panic(err)
}
return types.CommitID{
Version: version,
Hash: hash,
}
}
/ WorkingHash returns the hash of the current working tree.
func (st *Store)
WorkingHash() []byte {
return st.tree.WorkingHash()
}
/ LastCommitID implements Committer.
func (st *Store)
LastCommitID()
types.CommitID {
return types.CommitID{
Version: st.tree.Version(),
Hash: st.tree.Hash(),
}
}
/ SetPruning panics as pruning options should be provided at initialization
/ since IAVl accepts pruning options directly.
func (st *Store)
SetPruning(_ pruningtypes.PruningOptions) {
panic("cannot set pruning options on an initialized IAVL store")
}
/ SetPruning panics as pruning options should be provided at initialization
/ since IAVl accepts pruning options directly.
func (st *Store)
GetPruning()
pruningtypes.PruningOptions {
panic("cannot get pruning options on an initialized IAVL store")
}
/ VersionExists returns whether or not a given version is stored.
func (st *Store)
VersionExists(version int64)
bool {
return st.tree.VersionExists(version)
}
/ GetAllVersions returns all versions in the iavl tree
func (st *Store)
GetAllVersions() []int {
return st.tree.AvailableVersions()
}
/ Implements Store.
func (st *Store)
GetStoreType()
types.StoreType {
return types.StoreTypeIAVL
}
/ Implements Store.
func (st *Store)
CacheWrap()
types.CacheWrap {
return cachekv.NewStore(st)
}
/ CacheWrapWithTrace implements the Store interface.
func (st *Store)
CacheWrapWithTrace(w io.Writer, tc types.TraceContext)
types.CacheWrap {
return cachekv.NewStore(tracekv.NewStore(st, w, tc))
}
/ Implements types.KVStore.
func (st *Store)
Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
_, err := st.tree.Set(key, value)
if err != nil && st.logger != nil {
st.logger.Error("iavl set error", "error", err.Error())
}
}
/ Implements types.KVStore.
func (st *Store)
Get(key []byte) []byte {
defer st.metrics.MeasureSince("store", "iavl", "get")
value, err := st.tree.Get(key)
if err != nil {
panic(err)
}
return value
}
/ Implements types.KVStore.
func (st *Store)
Has(key []byte) (exists bool) {
defer st.metrics.MeasureSince("store", "iavl", "has")
has, err := st.tree.Has(key)
if err != nil {
panic(err)
}
return has
}
/ Implements types.KVStore.
func (st *Store)
Delete(key []byte) {
defer st.metrics.MeasureSince("store", "iavl", "delete")
_, _, err := st.tree.Remove(key)
if err != nil {
panic(err)
}
}
/ DeleteVersionsTo deletes versions upto the given version from the MutableTree. An error
/ is returned if any single version is invalid or the delete fails. All writes
/ happen in a single batch with a single commit.
func (st *Store)
DeleteVersionsTo(version int64)
error {
return st.tree.DeleteVersionsTo(version)
}
/ LoadVersionForOverwriting attempts to load a tree at a previously committed
/ version. Any versions greater than targetVersion will be deleted.
func (st *Store)
LoadVersionForOverwriting(targetVersion int64)
error {
return st.tree.LoadVersionForOverwriting(targetVersion)
}
/ Implements types.KVStore.
func (st *Store)
Iterator(start, end []byte)
types.Iterator {
iterator, err := st.tree.Iterator(start, end, true)
if err != nil {
panic(err)
}
return iterator
}
/ Implements types.KVStore.
func (st *Store)
ReverseIterator(start, end []byte)
types.Iterator {
iterator, err := st.tree.Iterator(start, end, false)
if err != nil {
panic(err)
}
return iterator
}
/ SetInitialVersion sets the initial version of the IAVL tree. It is used when
/ starting a new chain at an arbitrary height.
func (st *Store)
SetInitialVersion(version int64) {
st.tree.SetInitialVersion(uint64(version))
}
/ Exports the IAVL store at the given version, returning an iavl.Exporter for the tree.
func (st *Store)
Export(version int64) (*iavl.Exporter, error) {
istore, err := st.GetImmutable(version)
if err != nil {
return nil, errorsmod.Wrapf(err, "iavl export failed for version %v", version)
}
tree, ok := istore.tree.(*immutableTree)
if !ok || tree == nil {
return nil, fmt.Errorf("iavl export failed: unable to fetch tree for version %v", version)
}
return tree.Export()
}
/ Import imports an IAVL tree at the given version, returning an iavl.Importer for importing.
func (st *Store)
Import(version int64) (*iavl.Importer, error) {
tree, ok := st.tree.(*iavl.MutableTree)
if !ok {
return nil, errors.New("iavl import failed: unable to find mutable tree")
}
return tree.Import(version)
}
/ Handle gatest the latest height, if height is 0
func getHeight(tree Tree, req *types.RequestQuery)
int64 {
height := req.Height
if height == 0 {
latest := tree.Version()
if tree.VersionExists(latest - 1) {
height = latest - 1
}
else {
height = latest
}
}
return height
}
/ Query implements ABCI interface, allows queries
/
/ by default we will return from (latest height -1),
/ as we will have merkle proofs immediately (header height = data height + 1)
/ If latest-1 is not present, use latest (which must be present)
/ if you care to have the latest data to see a tx results, you must
/ explicitly set the height you want to see
func (st *Store)
Query(req *types.RequestQuery) (res *types.ResponseQuery, err error) {
defer st.metrics.MeasureSince("store", "iavl", "query")
if len(req.Data) == 0 {
return &types.ResponseQuery{
}, errorsmod.Wrap(types.ErrTxDecode, "query cannot be zero length")
}
tree := st.tree
/ store the height we chose in the response, with 0 being changed to the
/ latest height
res = &types.ResponseQuery{
Height: getHeight(tree, req),
}
switch req.Path {
case "/key": / get by key
key := req.Data / data holds the key bytes
res.Key = key
if !st.VersionExists(res.Height) {
res.Log = iavl.ErrVersionDoesNotExist.Error()
break
}
value, err := tree.GetVersioned(key, res.Height)
if err != nil {
panic(err)
}
res.Value = value
if !req.Prove {
break
}
/ Continue to prove existence/absence of value
/ Must convert store.Tree to iavl.MutableTree with given version to use in CreateProof
iTree, err := tree.GetImmutable(res.Height)
if err != nil {
/ sanity check: If value for given version was retrieved, immutable tree must also be retrievable
panic(fmt.Sprintf("version exists in store but could not retrieve corresponding versioned tree in store, %s", err.Error()))
}
mtree := &iavl.MutableTree{
ImmutableTree: iTree,
}
/ get proof from tree and convert to merkle.Proof before adding to result
res.ProofOps = getProofFromTree(mtree, req.Data, res.Value != nil)
case "/subspace":
pairs := kv.Pairs{
Pairs: make([]kv.Pair, 0),
}
subspace := req.Data
res.Key = subspace
iterator := types.KVStorePrefixIterator(st, subspace)
for ; iterator.Valid(); iterator.Next() {
pairs.Pairs = append(pairs.Pairs, kv.Pair{
Key: iterator.Key(),
Value: iterator.Value()
})
}
if err := iterator.Close(); err != nil {
panic(fmt.Errorf("failed to close iterator: %w", err))
}
bz, err := pairs.Marshal()
if err != nil {
panic(fmt.Errorf("failed to marshal KV pairs: %w", err))
}
res.Value = bz
default:
return &types.ResponseQuery{
}, errorsmod.Wrapf(types.ErrUnknownRequest, "unexpected query path: %v", req.Path)
}
return res, err
}
/ TraverseStateChanges traverses the state changes between two versions and calls the given function.
func (st *Store)
TraverseStateChanges(startVersion, endVersion int64, fn func(version int64, changeSet *iavl.ChangeSet)
error)
error {
return st.tree.TraverseStateChanges(startVersion, endVersion, fn)
}
/ Takes a MutableTree, a key, and a flag for creating existence or absence proof and returns the
/ appropriate merkle.Proof. Since this must be called after querying for the value, this function should never error
/ Thus, it will panic on error rather than returning it
func getProofFromTree(tree *iavl.MutableTree, key []byte, exists bool) *cmtprotocrypto.ProofOps {
var (
commitmentProof *ics23.CommitmentProof
err error
)
if exists {
/ value was found
commitmentProof, err = tree.GetMembershipProof(key)
if err != nil {
/ sanity check: If value was found, membership proof must be creatable
panic(fmt.Sprintf("unexpected value for empty proof: %s", err.Error()))
}
}
else {
/ value wasn't found
commitmentProof, err = tree.GetNonMembershipProof(key)
if err != nil {
/ sanity check: If value wasn't found, nonmembership proof must be creatable
panic(fmt.Sprintf("unexpected error for nonexistence proof: %s", err.Error()))
}
}
op := types.NewIavlCommitmentOp(key, commitmentProof)
return &cmtprotocrypto.ProofOps{
Ops: []cmtprotocrypto.ProofOp{
op.ProofOp()
}}
}
iavl
stores are based around an IAVL Tree, a self-balancing binary tree which guarantees that:
Get
andSet
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).
DbAdapter
Store
dbadapter.Store
is an adapter for dbm.DB
making it fulfilling the KVStore
interface.
Copy
Ask AI
package dbadapter
import (
"io"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
/ Wrapper type for dbm.Db with implementation of KVStore
type Store struct {
dbm.DB
}
/ Get wraps the underlying DB's Get method panicing on error.
func (dsa Store)
Get(key []byte) []byte {
v, err := dsa.DB.Get(key)
if err != nil {
panic(err)
}
return v
}
/ Has wraps the underlying DB's Has method panicing on error.
func (dsa Store)
Has(key []byte)
bool {
ok, err := dsa.DB.Has(key)
if err != nil {
panic(err)
}
return ok
}
/ Set wraps the underlying DB's Set method panicing on error.
func (dsa Store)
Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
if err := dsa.DB.Set(key, value); err != nil {
panic(err)
}
}
/ Delete wraps the underlying DB's Delete method panicing on error.
func (dsa Store)
Delete(key []byte) {
if err := dsa.DB.Delete(key); err != nil {
panic(err)
}
}
/ Iterator wraps the underlying DB's Iterator method panicing on error.
func (dsa Store)
Iterator(start, end []byte)
types.Iterator {
iter, err := dsa.DB.Iterator(start, end)
if err != nil {
panic(err)
}
return iter
}
/ ReverseIterator wraps the underlying DB's ReverseIterator method panicing on error.
func (dsa Store)
ReverseIterator(start, end []byte)
types.Iterator {
iter, err := dsa.DB.ReverseIterator(start, end)
if err != nil {
panic(err)
}
return iter
}
/ GetStoreType returns the type of the store.
func (Store)
GetStoreType()
types.StoreType {
return types.StoreTypeDB
}
/ CacheWrap branches the underlying store.
func (dsa Store)
CacheWrap()
types.CacheWrap {
return cachekv.NewStore(dsa)
}
/ CacheWrapWithTrace implements KVStore.
func (dsa Store)
CacheWrapWithTrace(w io.Writer, tc types.TraceContext)
types.CacheWrap {
return cachekv.NewStore(tracekv.NewStore(dsa, w, tc))
}
/ dbm.DB implements KVStore so we can CacheKVStore it.
var _ types.KVStore = Store{
}
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
Ask AI
package transient
import (
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/store/dbadapter"
pruningtypes "cosmossdk.io/store/pruning/types"
"cosmossdk.io/store/types"
)
var (
_ types.Committer = (*Store)(nil)
_ types.KVStore = (*Store)(nil)
)
/ Store is a wrapper for a MemDB with Commiter implementation
type Store struct {
dbadapter.Store
}
/ Constructs new MemDB adapter
func NewStore() *Store {
return &Store{
Store: dbadapter.Store{
DB: dbm.NewMemDB()
}}
}
/ Implements CommitStore
/ Commit cleans up Store.
func (ts *Store)
Commit() (id types.CommitID) {
ts.Store = dbadapter.Store{
DB: dbm.NewMemDB()
}
return
}
func (ts *Store)
SetPruning(_ pruningtypes.PruningOptions) {
}
/ GetPruning is a no-op as pruning options cannot be directly set on this store.
/ They must be set on the root commit multi-store.
func (ts *Store)
GetPruning()
pruningtypes.PruningOptions {
return pruningtypes.NewPruningOptions(pruningtypes.PruningUndefined)
}
/ Implements CommitStore
func (ts *Store)
LastCommitID()
types.CommitID {
return types.CommitID{
}
}
func (ts *Store)
WorkingHash() []byte {
return []byte{
}
}
/ Implements Store.
func (ts *Store)
GetStoreType()
types.StoreType {
return types.StoreTypeTransient
}
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
Ask AI
package types
import (
"fmt"
"maps"
"reflect"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
/ StoreKey is the string store key for the param store
StoreKey = "params"
/ TStoreKey is the string store key for the param transient store
TStoreKey = "transient_params"
)
/ 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
}
/ NewSubspace constructs a store with namestore
func NewSubspace(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey, name string)
Subspace {
return Subspace{
cdc: cdc,
legacyAmino: legacyAmino,
key: key,
tkey: tkey,
name: []byte(name),
table: NewKeyTable(),
}
}
/ HasKeyTable returns if the Subspace has a KeyTable registered.
func (s Subspace)
HasKeyTable()
bool {
return len(s.table.m) > 0
}
/ WithKeyTable initializes KeyTable and returns modified Subspace
func (s Subspace)
WithKeyTable(table KeyTable)
Subspace {
if table.m == nil {
panic("WithKeyTable()
called with nil KeyTable")
}
if len(s.table.m) != 0 {
panic("WithKeyTable()
called on already initialized Subspace")
}
maps.Copy(s.table.m, table.m)
/ Allocate additional capacity for Subspace.name
/ So we don't have to allocate extra space each time appending to the key
name := s.name
s.name = make([]byte, len(name), len(name)+table.maxKeyLength())
copy(s.name, name)
return s
}
/ Returns a KVStore identical with ctx.KVStore(s.key).Prefix()
func (s Subspace)
kvStore(ctx sdk.Context)
storetypes.KVStore {
/ append here is safe, appends within a function won't cause
/ weird side effects when its singlethreaded
return prefix.NewStore(ctx.KVStore(s.key), append(s.name, '/'))
}
/ Returns a transient store for modification
func (s Subspace)
transientStore(ctx sdk.Context)
storetypes.KVStore {
/ append here is safe, appends within a function won't cause
/ weird side effects when its singlethreaded
return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/'))
}
/ Validate attempts to validate a parameter value by its key. If the key is not
/ registered or if the validation of the value fails, an error is returned.
func (s Subspace)
Validate(ctx sdk.Context, key []byte, value any)
error {
attr, ok := s.table.m[string(key)]
if !ok {
return fmt.Errorf("parameter %s not registered", key)
}
if err := attr.vfn(value); err != nil {
return fmt.Errorf("invalid parameter value: %w", err)
}
return nil
}
/ Get queries for a parameter by key from the Subspace's KVStore and sets the
/ value to the provided pointer. If the value does not exist, it will panic.
func (s Subspace)
Get(ctx sdk.Context, key []byte, ptr any) {
s.checkType(key, ptr)
store := s.kvStore(ctx)
bz := store.Get(key)
if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil {
panic(err)
}
}
/ GetIfExists queries for a parameter by key from the Subspace's KVStore and
/ sets the value to the provided pointer. If the value does not exist, it will
/ perform a no-op.
func (s Subspace)
GetIfExists(ctx sdk.Context, key []byte, ptr any) {
store := s.kvStore(ctx)
bz := store.Get(key)
if bz == nil {
return
}
s.checkType(key, ptr)
if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil {
panic(err)
}
}
/ IterateKeys iterates over all the keys in the subspace and executes the
/ provided callback. If the callback returns true for a given key, iteration
/ will halt.
func (s Subspace)
IterateKeys(ctx sdk.Context, cb func(key []byte)
bool) {
store := s.kvStore(ctx)
iter := storetypes.KVStorePrefixIterator(store, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if cb(iter.Key()) {
break
}
}
}
/ GetRaw queries for the raw values bytes for a parameter by key.
func (s Subspace)
GetRaw(ctx sdk.Context, key []byte) []byte {
store := s.kvStore(ctx)
return store.Get(key)
}
/ Has returns if a parameter key exists or not in the Subspace's KVStore.
func (s Subspace)
Has(ctx sdk.Context, key []byte)
bool {
store := s.kvStore(ctx)
return store.Has(key)
}
/ Modified returns true if the parameter key is set in the Subspace's transient
/ KVStore.
func (s Subspace)
Modified(ctx sdk.Context, key []byte)
bool {
tstore := s.transientStore(ctx)
return tstore.Has(key)
}
/ checkType verifies that the provided key and value are comptable and registered.
func (s Subspace)
checkType(key []byte, value any) {
attr, ok := s.table.m[string(key)]
if !ok {
panic(fmt.Sprintf("parameter %s not registered", key))
}
ty := attr.ty
pty := reflect.TypeOf(value)
if pty.Kind() == reflect.Ptr {
pty = pty.Elem()
}
if pty != ty {
panic("type mismatch with registered table")
}
}
/ Set stores a value for given a parameter key assuming the parameter type has
/ been registered. It will panic if the parameter type has not been registered
/ or if the value cannot be encoded. A change record is also set in the Subspace's
/ transient KVStore to mark the parameter as modified.
func (s Subspace)
Set(ctx sdk.Context, key []byte, value any) {
s.checkType(key, value)
store := s.kvStore(ctx)
bz, err := s.legacyAmino.MarshalJSON(value)
if err != nil {
panic(err)
}
store.Set(key, bz)
tstore := s.transientStore(ctx)
tstore.Set(key, []byte{
})
}
/ Update stores an updated raw value for a given parameter key assuming the
/ parameter type has been registered. It will panic if the parameter type has
/ not been registered or if the value cannot be encoded. An error is returned
/ if the raw value is not compatible with the registered type for the parameter
/ key or if the new value is invalid as determined by the registered type's
/ validation function.
func (s Subspace)
Update(ctx sdk.Context, key, value []byte)
error {
attr, ok := s.table.m[string(key)]
if !ok {
panic(fmt.Sprintf("parameter %s not registered", key))
}
ty := attr.ty
dest := reflect.New(ty).Interface()
s.GetIfExists(ctx, key, dest)
if err := s.legacyAmino.UnmarshalJSON(value, dest); err != nil {
return err
}
/ destValue contains the dereferenced value of dest so validation function do
/ not have to operate on pointers.
destValue := reflect.Indirect(reflect.ValueOf(dest)).Interface()
if err := s.Validate(ctx, key, destValue); err != nil {
return err
}
s.Set(ctx, key, dest)
return nil
}
/ GetParamSet iterates through each ParamSetPair where for each pair, it will
/ retrieve the value and set it to the corresponding value pointer provided
/ in the ParamSetPair by calling Subspace#Get.
func (s Subspace)
GetParamSet(ctx sdk.Context, ps ParamSet) {
for _, pair := range ps.ParamSetPairs() {
s.Get(ctx, pair.Key, pair.Value)
}
}
/ GetParamSetIfExists iterates through each ParamSetPair where for each pair, it will
/ retrieve the value and set it to the corresponding value pointer provided
/ in the ParamSetPair by calling Subspace#GetIfExists.
func (s Subspace)
GetParamSetIfExists(ctx sdk.Context, ps ParamSet) {
for _, pair := range ps.ParamSetPairs() {
s.GetIfExists(ctx, pair.Key, pair.Value)
}
}
/ SetParamSet iterates through each ParamSetPair and sets the value with the
/ corresponding parameter key in the Subspace's KVStore.
func (s Subspace)
SetParamSet(ctx sdk.Context, ps ParamSet) {
for _, pair := range ps.ParamSetPairs() {
/ pair.Field is a pointer to the field, so indirecting the ptr.
/ go-amino automatically handles it but just for sure,
/ since SetStruct is meant to be used in InitGenesis
/ so this method will not be called frequently
v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface()
if err := pair.ValidatorFn(v); err != nil {
panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err))
}
s.Set(ctx, pair.Key, v)
}
}
/ Name returns the name of the Subspace.
func (s Subspace)
Name()
string {
return string(s.name)
}
/ Wrapper of Subspace, provides immutable functions only
type ReadOnlySubspace struct {
s Subspace
}
/ Get delegates a read-only Get call to the Subspace.
func (ros ReadOnlySubspace)
Get(ctx sdk.Context, key []byte, ptr any) {
ros.s.Get(ctx, key, ptr)
}
/ GetRaw delegates a read-only GetRaw call to the Subspace.
func (ros ReadOnlySubspace)
GetRaw(ctx sdk.Context, key []byte) []byte {
return ros.s.GetRaw(ctx, key)
}
/ Has delegates a read-only Has call to the Subspace.
func (ros ReadOnlySubspace)
Has(ctx sdk.Context, key []byte)
bool {
return ros.s.Has(ctx, key)
}
/ Modified delegates a read-only Modified call to the Subspace.
func (ros ReadOnlySubspace)
Modified(ctx sdk.Context, key []byte)
bool {
return ros.s.Modified(ctx, key)
}
/ Name delegates a read-only Name call to the Subspace.
func (ros ReadOnlySubspace)
Name()
string {
return ros.s.Name()
}
context
via the TransientStore()
method:
Copy
Ask AI
package types
import (
"context"
"time"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"cosmossdk.io/core/comet"
"cosmossdk.io/core/header"
"cosmossdk.io/log"
"cosmossdk.io/store/gaskv"
storetypes "cosmossdk.io/store/types"
)
/ ExecMode defines the execution mode which can be set on a Context.
type ExecMode uint8
/ All possible execution modes.
const (
ExecModeCheck ExecMode = iota
ExecModeReCheck
ExecModeSimulate
ExecModePrepareProposal
ExecModeProcessProposal
ExecModeVoteExtension
ExecModeVerifyVoteExtension
ExecModeFinalize
)
/*
Context is an immutable object contains all information needed to
process a request.
It contains a context.Context object inside if you want to use that,
but please do not over-use it. We try to keep all data structured
and standard additions here would be better just to add to the Context struct
*/
type Context struct {
baseCtx context.Context
ms storetypes.MultiStore
/ Deprecated: Use HeaderService for height, time, and chainID and CometService for the rest
header cmtproto.Header
/ Deprecated: Use HeaderService for hash
headerHash []byte
/ Deprecated: Use HeaderService for chainID and CometService for the rest
chainID string
txBytes []byte
logger log.Logger
voteInfo []abci.VoteInfo
gasMeter storetypes.GasMeter
blockGasMeter storetypes.GasMeter
checkTx bool
recheckTx bool / if recheckTx == true, then checkTx must also be true
sigverifyTx bool / when run simulation, because the private key corresponding to the account in the genesis.json randomly generated, we must skip the sigverify.
execMode ExecMode
minGasPrice DecCoins
consParams cmtproto.ConsensusParams
eventManager EventManagerI
priority int64 / The tx priority, only relevant in CheckTx
kvGasConfig storetypes.GasConfig
transientKVGasConfig storetypes.GasConfig
streamingManager storetypes.StreamingManager
cometInfo comet.BlockInfo
headerInfo header.Info
}
/ Proposed rename, not done to avoid API breakage
type Request = Context
/ Read-only accessors
func (c Context)
Context()
context.Context {
return c.baseCtx
}
func (c Context)
MultiStore()
storetypes.MultiStore {
return c.ms
}
func (c Context)
BlockHeight()
int64 {
return c.header.Height
}
func (c Context)
BlockTime()
time.Time {
return c.header.Time
}
func (c Context)
ChainID()
string {
return c.chainID
}
func (c Context)
TxBytes() []byte {
return c.txBytes
}
func (c Context)
Logger()
log.Logger {
return c.logger
}
func (c Context)
VoteInfos() []abci.VoteInfo {
return c.voteInfo
}
func (c Context)
GasMeter()
storetypes.GasMeter {
return c.gasMeter
}
func (c Context)
BlockGasMeter()
storetypes.GasMeter {
return c.blockGasMeter
}
func (c Context)
IsCheckTx()
bool {
return c.checkTx
}
func (c Context)
IsReCheckTx()
bool {
return c.recheckTx
}
func (c Context)
IsSigverifyTx()
bool {
return c.sigverifyTx
}
func (c Context)
ExecMode()
ExecMode {
return c.execMode
}
func (c Context)
MinGasPrices()
DecCoins {
return c.minGasPrice
}
func (c Context)
EventManager()
EventManagerI {
return c.eventManager
}
func (c Context)
Priority()
int64 {
return c.priority
}
func (c Context)
KVGasConfig()
storetypes.GasConfig {
return c.kvGasConfig
}
func (c Context)
TransientKVGasConfig()
storetypes.GasConfig {
return c.transientKVGasConfig
}
func (c Context)
StreamingManager()
storetypes.StreamingManager {
return c.streamingManager
}
func (c Context)
CometInfo()
comet.BlockInfo {
return c.cometInfo
}
func (c Context)
HeaderInfo()
header.Info {
return c.headerInfo
}
/ BlockHeader returns the header by value.
func (c Context)
BlockHeader()
cmtproto.Header {
return c.header
}
/ HeaderHash returns a copy of the header hash obtained during abci.RequestBeginBlock
func (c Context)
HeaderHash() []byte {
hash := make([]byte, len(c.headerHash))
copy(hash, c.headerHash)
return hash
}
func (c Context)
ConsensusParams()
cmtproto.ConsensusParams {
return c.consParams
}
func (c Context)
Deadline() (deadline time.Time, ok bool) {
return c.baseCtx.Deadline()
}
func (c Context)
Done() <-chan struct{
} {
return c.baseCtx.Done()
}
func (c Context)
Err()
error {
return c.baseCtx.Err()
}
/ create a new context
func NewContext(ms storetypes.MultiStore, header cmtproto.Header, isCheckTx bool, logger log.Logger)
Context {
/ https://github.com/gogo/protobuf/issues/519
header.Time = header.Time.UTC()
return Context{
baseCtx: context.Background(),
ms: ms,
header: header,
chainID: header.ChainID,
checkTx: isCheckTx,
sigverifyTx: true,
logger: logger,
gasMeter: storetypes.NewInfiniteGasMeter(),
minGasPrice: DecCoins{
},
eventManager: NewEventManager(),
kvGasConfig: storetypes.KVGasConfig(),
transientKVGasConfig: storetypes.TransientGasConfig(),
}
}
/ WithContext returns a Context with an updated context.Context.
func (c Context)
WithContext(ctx context.Context)
Context {
c.baseCtx = ctx
return c
}
/ WithMultiStore returns a Context with an updated MultiStore.
func (c Context)
WithMultiStore(ms storetypes.MultiStore)
Context {
c.ms = ms
return c
}
/ WithBlockHeader returns a Context with an updated CometBFT block header in UTC time.
func (c Context)
WithBlockHeader(header cmtproto.Header)
Context {
/ https://github.com/gogo/protobuf/issues/519
header.Time = header.Time.UTC()
c.header = header
return c
}
/ WithHeaderHash returns a Context with an updated CometBFT block header hash.
func (c Context)
WithHeaderHash(hash []byte)
Context {
temp := make([]byte, len(hash))
copy(temp, hash)
c.headerHash = temp
return c
}
/ WithBlockTime returns a Context with an updated CometBFT block header time in UTC with no monotonic component.
/ Stripping the monotonic component is for time equality.
func (c Context)
WithBlockTime(newTime time.Time)
Context {
newHeader := c.BlockHeader()
/ https://github.com/gogo/protobuf/issues/519
newHeader.Time = newTime.Round(0).UTC()
return c.WithBlockHeader(newHeader)
}
/ WithProposer returns a Context with an updated proposer consensus address.
func (c Context)
WithProposer(addr ConsAddress)
Context {
newHeader := c.BlockHeader()
newHeader.ProposerAddress = addr.Bytes()
return c.WithBlockHeader(newHeader)
}
/ WithBlockHeight returns a Context with an updated block height.
func (c Context)
WithBlockHeight(height int64)
Context {
newHeader := c.BlockHeader()
newHeader.Height = height
return c.WithBlockHeader(newHeader)
}
/ WithChainID returns a Context with an updated chain identifier.
func (c Context)
WithChainID(chainID string)
Context {
c.chainID = chainID
return c
}
/ WithTxBytes returns a Context with an updated txBytes.
func (c Context)
WithTxBytes(txBytes []byte)
Context {
c.txBytes = txBytes
return c
}
/ WithLogger returns a Context with an updated logger.
func (c Context)
WithLogger(logger log.Logger)
Context {
c.logger = logger
return c
}
/ WithVoteInfos returns a Context with an updated consensus VoteInfo.
func (c Context)
WithVoteInfos(voteInfo []abci.VoteInfo)
Context {
c.voteInfo = voteInfo
return c
}
/ WithGasMeter returns a Context with an updated transaction GasMeter.
func (c Context)
WithGasMeter(meter storetypes.GasMeter)
Context {
c.gasMeter = meter
return c
}
/ WithBlockGasMeter returns a Context with an updated block GasMeter
func (c Context)
WithBlockGasMeter(meter storetypes.GasMeter)
Context {
c.blockGasMeter = meter
return c
}
/ WithKVGasConfig returns a Context with an updated gas configuration for
/ the KVStore
func (c Context)
WithKVGasConfig(gasConfig storetypes.GasConfig)
Context {
c.kvGasConfig = gasConfig
return c
}
/ WithTransientKVGasConfig returns a Context with an updated gas configuration for
/ the transient KVStore
func (c Context)
WithTransientKVGasConfig(gasConfig storetypes.GasConfig)
Context {
c.transientKVGasConfig = gasConfig
return c
}
/ WithIsCheckTx enables or disables CheckTx value for verifying transactions and returns an updated Context
func (c Context)
WithIsCheckTx(isCheckTx bool)
Context {
c.checkTx = isCheckTx
c.execMode = ExecModeCheck
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
c.execMode = ExecModeReCheck
return c
}
/ WithIsSigverifyTx called with true will sigverify in auth module
func (c Context)
WithIsSigverifyTx(isSigverifyTx bool)
Context {
c.sigverifyTx = isSigverifyTx
return c
}
/ WithExecMode returns a Context with an updated ExecMode.
func (c Context)
WithExecMode(m ExecMode)
Context {
c.execMode = m
return c
}
/ WithMinGasPrices returns a Context with an updated minimum gas price value
func (c Context)
WithMinGasPrices(gasPrices DecCoins)
Context {
c.minGasPrice = gasPrices
return c
}
/ WithConsensusParams returns a Context with an updated consensus params
func (c Context)
WithConsensusParams(params cmtproto.ConsensusParams)
Context {
c.consParams = params
return c
}
/ WithEventManager returns a Context with an updated event manager
func (c Context)
WithEventManager(em EventManagerI)
Context {
c.eventManager = em
return c
}
/ WithPriority returns a Context with an updated tx priority
func (c Context)
WithPriority(p int64)
Context {
c.priority = p
return c
}
/ WithStreamingManager returns a Context with an updated streaming manager
func (c Context)
WithStreamingManager(sm storetypes.StreamingManager)
Context {
c.streamingManager = sm
return c
}
/ WithCometInfo returns a Context with an updated comet info
func (c Context)
WithCometInfo(cometInfo comet.BlockInfo)
Context {
c.cometInfo = cometInfo
return c
}
/ WithHeaderInfo returns a Context with an updated header info
func (c Context)
WithHeaderInfo(headerInfo header.Info)
Context {
/ Settime to UTC
headerInfo.Time = headerInfo.Time.UTC()
c.headerInfo = headerInfo
return c
}
/ TODO: remove???
func (c Context)
IsZero()
bool {
return c.ms == nil
}
func (c Context)
WithValue(key, value any)
Context {
c.baseCtx = context.WithValue(c.baseCtx, key, value)
return c
}
func (c Context)
Value(key any)
any {
if key == SdkContextKey {
return c
}
return c.baseCtx.Value(key)
}
/ ----------------------------------------------------------------------------
/ Store / Caching
/ ----------------------------------------------------------------------------
/ KVStore fetches a KVStore from the MultiStore.
func (c Context)
KVStore(key storetypes.StoreKey)
storetypes.KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.kvGasConfig)
}
/ TransientStore fetches a TransientStore from the MultiStore.
func (c Context)
TransientStore(key storetypes.StoreKey)
storetypes.KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.transientKVGasConfig)
}
/ CacheContext returns a new Context with the multi-store cached and a new
/ EventManager. The cached context is written to the context when writeCache
/ is called. Note, events are automatically emitted on the parent context's
/ EventManager when the caller executes the write.
func (c Context)
CacheContext() (cc Context, writeCache func()) {
cms := c.ms.CacheMultiStore()
cc = c.WithMultiStore(cms).WithEventManager(NewEventManager())
writeCache = func() {
c.EventManager().EmitEvents(cc.EventManager().Events())
cms.Write()
}
return cc, writeCache
}
var (
_ context.Context = Context{
}
_ storetypes.Context = Context{
}
)
/ ContextKey defines a type alias for a stdlib Context key.
type ContextKey string
/ SdkContextKey is the key in the context.Context which holds the sdk.Context.
const SdkContextKey ContextKey = "sdk-context"
/ WrapSDKContext returns a stdlib context.Context with the provided sdk.Context's internal
/ context as a value. It is useful for passing an sdk.Context through methods that take a
/ stdlib context.Context parameter such as generated gRPC methods. To get the original
/ sdk.Context back, call UnwrapSDKContext.
/
/ Deprecated: there is no need to wrap anymore as the Cosmos SDK context implements context.Context.
func WrapSDKContext(ctx Context)
context.Context {
return ctx
}
/ UnwrapSDKContext retrieves a Context from a context.Context instance
/ attached with WrapSDKContext. It panics if a Context was not properly
/ attached
func UnwrapSDKContext(ctx context.Context)
Context {
if sdkCtx, ok := ctx.(Context); ok {
return sdkCtx
}
return ctx.Value(SdkContextKey).(Context)
}
KVStore Wrappers
CacheKVStore
cachekv.Store
is a wrapper KVStore
which provides buffered writing / cached reading functionalities over the underlying KVStore
.
Copy
Ask AI
package cachekv
import (
"bytes"
"io"
"sort"
"sync"
dbm "github.com/cosmos/cosmos-db"
"cosmossdk.io/math"
"cosmossdk.io/store/cachekv/internal"
"cosmossdk.io/store/internal/conv"
"cosmossdk.io/store/internal/kv"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
/ cValue represents a cached value.
/ If dirty is true, it indicates the cached value is different from the underlying value.
type cValue struct {
value []byte
dirty bool
}
/ Store wraps an in-memory cache around an underlying types.KVStore.
type Store struct {
mtx sync.Mutex
cache map[string]*cValue
unsortedCache map[string]struct{
}
sortedCache internal.BTree / always ascending sorted
parent types.KVStore
}
var _ types.CacheKVStore = (*Store)(nil)
/ NewStore creates a new Store object
func NewStore(parent types.KVStore) *Store {
return &Store{
cache: make(map[string]*cValue),
unsortedCache: make(map[string]struct{
}),
sortedCache: internal.NewBTree(),
parent: parent,
}
}
/ GetStoreType implements Store.
func (store *Store)
GetStoreType()
types.StoreType {
return store.parent.GetStoreType()
}
/ Get implements types.KVStore.
func (store *Store)
Get(key []byte) (value []byte) {
store.mtx.Lock()
defer store.mtx.Unlock()
types.AssertValidKey(key)
cacheValue, ok := store.cache[conv.UnsafeBytesToStr(key)]
if !ok {
value = store.parent.Get(key)
store.setCacheValue(key, value, false)
}
else {
value = cacheValue.value
}
return value
}
/ Set implements types.KVStore.
func (store *Store)
Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
store.mtx.Lock()
defer store.mtx.Unlock()
store.setCacheValue(key, value, true)
}
/ Has implements types.KVStore.
func (store *Store)
Has(key []byte)
bool {
value := store.Get(key)
return value != nil
}
/ Delete implements types.KVStore.
func (store *Store)
Delete(key []byte) {
types.AssertValidKey(key)
store.mtx.Lock()
defer store.mtx.Unlock()
store.setCacheValue(key, nil, true)
}
func (store *Store)
resetCaches() {
if len(store.cache) > 100_000 {
/ Cache is too large. We likely did something linear time
/ (e.g. Epoch block, Genesis block, etc). Free the old caches from memory, and let them get re-allocated.
/ TODO: In a future CacheKV redesign, such linear workloads should get into a different cache instantiation.
/ 100_000 is arbitrarily chosen as it solved Osmosis' InitGenesis RAM problem.
store.cache = make(map[string]*cValue)
store.unsortedCache = make(map[string]struct{
})
}
else {
/ Clear the cache using the map clearing idiom
/ and not allocating fresh objects.
/ Please see https://bencher.orijtech.com/perfclinic/mapclearing/
for key := range store.cache {
delete(store.cache, key)
}
for key := range store.unsortedCache {
delete(store.unsortedCache, key)
}
}
store.sortedCache = internal.NewBTree()
}
/ Implements Cachetypes.KVStore.
func (store *Store)
Write() {
store.mtx.Lock()
defer store.mtx.Unlock()
if len(store.cache) == 0 && len(store.unsortedCache) == 0 {
store.sortedCache = internal.NewBTree()
return
}
type cEntry struct {
key string
val *cValue
}
/ We need a copy of all of the keys.
/ Not the best. To reduce RAM pressure, we copy the values as well
/ and clear out the old caches right after the copy.
sortedCache := make([]cEntry, 0, len(store.cache))
for key, dbValue := range store.cache {
if dbValue.dirty {
sortedCache = append(sortedCache, cEntry{
key, dbValue
})
}
}
store.resetCaches()
sort.Slice(sortedCache, func(i, j int)
bool {
return sortedCache[i].key < sortedCache[j].key
})
/ TODO: Consider allowing usage of Batch, which would allow the write to
/ at least happen atomically.
for _, obj := range sortedCache {
/ We use []byte(key)
instead of conv.UnsafeStrToBytes because we cannot
/ be sure if the underlying store might do a save with the byteslice or
/ not. Once we get confirmation that .Delete is guaranteed not to
/ save the byteslice, then we can assume only a read-only copy is sufficient.
if obj.val.value != nil {
/ It already exists in the parent, hence update it.
store.parent.Set([]byte(obj.key), obj.val.value)
}
else {
store.parent.Delete([]byte(obj.key))
}
}
}
/ CacheWrap implements CacheWrapper.
func (store *Store)
CacheWrap()
types.CacheWrap {
return NewStore(store)
}
/ CacheWrapWithTrace implements the CacheWrapper interface.
func (store *Store)
CacheWrapWithTrace(w io.Writer, tc types.TraceContext)
types.CacheWrap {
return NewStore(tracekv.NewStore(store, w, tc))
}
/----------------------------------------
/ Iteration
/ Iterator implements types.KVStore.
func (store *Store)
Iterator(start, end []byte)
types.Iterator {
return store.iterator(start, end, true)
}
/ ReverseIterator implements types.KVStore.
func (store *Store)
ReverseIterator(start, end []byte)
types.Iterator {
return store.iterator(start, end, false)
}
func (store *Store)
iterator(start, end []byte, ascending bool)
types.Iterator {
store.mtx.Lock()
defer store.mtx.Unlock()
store.dirtyItems(start, end)
isoSortedCache := store.sortedCache.Copy()
var (
err error
parent, cache types.Iterator
)
if ascending {
parent = store.parent.Iterator(start, end)
cache, err = isoSortedCache.Iterator(start, end)
}
else {
parent = store.parent.ReverseIterator(start, end)
cache, err = isoSortedCache.ReverseIterator(start, end)
}
if err != nil {
panic(err)
}
return internal.NewCacheMergeIterator(parent, cache, ascending)
}
func findStartIndex(strL []string, startQ string)
int {
/ Modified binary search to find the very first element in >=startQ.
if len(strL) == 0 {
return -1
}
var left, right, mid int
right = len(strL) - 1
for left <= right {
mid = (left + right) >> 1
midStr := strL[mid]
if midStr == startQ {
/ Handle condition where there might be multiple values equal to startQ.
/ We are looking for the very first value < midStL, that i+1 will be the first
/ element >= midStr.
for i := mid - 1; i >= 0; i-- {
if strL[i] != midStr {
return i + 1
}
}
return 0
}
if midStr < startQ {
left = mid + 1
}
else { / midStrL > startQ
right = mid - 1
}
}
if left >= 0 && left < len(strL) && strL[left] >= startQ {
return left
}
return -1
}
func findEndIndex(strL []string, endQ string)
int {
if len(strL) == 0 {
return -1
}
/ Modified binary search to find the very first element <endQ.
var left, right, mid int
right = len(strL) - 1
for left <= right {
mid = (left + right) >> 1
midStr := strL[mid]
if midStr == endQ {
/ Handle condition where there might be multiple values equal to startQ.
/ We are looking for the very first value < midStL, that i+1 will be the first
/ element >= midStr.
for i := mid - 1; i >= 0; i-- {
if strL[i] < midStr {
return i + 1
}
}
return 0
}
if midStr < endQ {
left = mid + 1
}
else { / midStrL > startQ
right = mid - 1
}
}
/ Binary search failed, now let's find a value less than endQ.
for i := right; i >= 0; i-- {
if strL[i] < endQ {
return i
}
}
return -1
}
type sortState int
const (
stateUnsorted sortState = iota
stateAlreadySorted
)
const minSortSize = 1024
/ Constructs a slice of dirty items, to use w/ memIterator.
func (store *Store)
dirtyItems(start, end []byte) {
startStr, endStr := conv.UnsafeBytesToStr(start), conv.UnsafeBytesToStr(end)
if end != nil && startStr > endStr {
/ Nothing to do here.
return
}
n := len(store.unsortedCache)
unsorted := make([]*kv.Pair, 0)
/ If the unsortedCache is too big, its costs too much to determine
/ whats in the subset we are concerned about.
/ If you are interleaving iterator calls with writes, this can easily become an
/ O(N^2)
overhead.
/ Even without that, too many range checks eventually becomes more expensive
/ than just not having the cache.
if n < minSortSize {
for key := range store.unsortedCache {
/ dbm.IsKeyInDomain is nil safe and returns true iff key is greater than start
if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) {
cacheValue := store.cache[key]
unsorted = append(unsorted, &kv.Pair{
Key: []byte(key),
Value: cacheValue.value
})
}
}
store.clearUnsortedCacheSubset(unsorted, stateUnsorted)
return
}
/ Otherwise it is large so perform a modified binary search to find
/ the target ranges for the keys that we should be looking for.
strL := make([]string, 0, n)
for key := range store.unsortedCache {
strL = append(strL, key)
}
sort.Strings(strL)
/ Now find the values within the domain
/ [start, end)
startIndex := findStartIndex(strL, startStr)
if startIndex < 0 {
startIndex = 0
}
var endIndex int
if end == nil {
endIndex = len(strL) - 1
}
else {
endIndex = findEndIndex(strL, endStr)
}
if endIndex < 0 {
endIndex = len(strL) - 1
}
/ Since we spent cycles to sort the values, we should process and remove a reasonable amount
/ ensure start to end is at least minSortSize in size
/ if below minSortSize, expand it to cover additional values
/ this amortizes the cost of processing elements across multiple calls
if endIndex-startIndex < minSortSize {
endIndex = math.Min(startIndex+minSortSize, len(strL)-1)
if endIndex-startIndex < minSortSize {
startIndex = math.Max(endIndex-minSortSize, 0)
}
}
kvL := make([]*kv.Pair, 0, 1+endIndex-startIndex)
for i := startIndex; i <= endIndex; i++ {
key := strL[i]
cacheValue := store.cache[key]
kvL = append(kvL, &kv.Pair{
Key: []byte(key),
Value: cacheValue.value
})
}
/ kvL was already sorted so pass it in as is.
store.clearUnsortedCacheSubset(kvL, stateAlreadySorted)
}
func (store *Store)
clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sortState) {
n := len(store.unsortedCache)
if len(unsorted) == n { / This pattern allows the Go compiler to emit the map clearing idiom for the entire map.
for key := range store.unsortedCache {
delete(store.unsortedCache, key)
}
}
else { / Otherwise, normally delete the unsorted keys from the map.
for _, kv := range unsorted {
delete(store.unsortedCache, conv.UnsafeBytesToStr(kv.Key))
}
}
if sortState == stateUnsorted {
sort.Slice(unsorted, func(i, j int)
bool {
return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0
})
}
for _, item := range unsorted {
/ sortedCache is able to store `nil` value to represent deleted items.
store.sortedCache.Set(item.Key, item.Value)
}
}
/----------------------------------------
/ etc
/ Only entrypoint to mutate store.cache.
/ A `nil` value means a deletion.
func (store *Store)
setCacheValue(key, value []byte, dirty bool) {
keyStr := conv.UnsafeBytesToStr(key)
store.cache[keyStr] = &cValue{
value: value,
dirty: dirty,
}
if dirty {
store.unsortedCache[keyStr] = struct{
}{
}
}
}
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()
has 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
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
Ask AI
package gaskv
import (
"io"
"cosmossdk.io/store/types"
)
var _ types.KVStore = &Store{
}
/ 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
}
/ NewStore returns a reference to a new GasKVStore.
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store {
kvs := &Store{
gasMeter: gasMeter,
gasConfig: gasConfig,
parent: parent,
}
return kvs
}
/ Implements Store.
func (gs *Store)
GetStoreType()
types.StoreType {
return gs.parent.GetStoreType()
}
/ Implements KVStore.
func (gs *Store)
Get(key []byte) (value []byte) {
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc)
value = gs.parent.Get(key)
/ TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc)
return value
}
/ Implements KVStore.
func (gs *Store)
Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc)
/ TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(key)), types.GasWritePerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc)
gs.parent.Set(key, value)
}
/ Implements KVStore.
func (gs *Store)
Has(key []byte)
bool {
gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc)
return gs.parent.Has(key)
}
/ Implements KVStore.
func (gs *Store)
Delete(key []byte) {
/ charge gas to prevent certain attack vectors even though space is being freed
gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc)
gs.parent.Delete(key)
}
/ Iterator implements the KVStore interface. It returns an iterator which
/ incurs a flat gas cost for seeking to the first key/value pair and a variable
/ gas cost based on the current value's length if the iterator is valid.
func (gs *Store)
Iterator(start, end []byte)
types.Iterator {
return gs.iterator(start, end, true)
}
/ ReverseIterator implements the KVStore interface. It returns a reverse
/ iterator which incurs a flat gas cost for seeking to the first key/value pair
/ and a variable gas cost based on the current value's length if the iterator
/ is valid.
func (gs *Store)
ReverseIterator(start, end []byte)
types.Iterator {
return gs.iterator(start, end, false)
}
/ Implements KVStore.
func (gs *Store)
CacheWrap()
types.CacheWrap {
panic("cannot CacheWrap a GasKVStore")
}
/ CacheWrapWithTrace implements the KVStore interface.
func (gs *Store)
CacheWrapWithTrace(_ io.Writer, _ types.TraceContext)
types.CacheWrap {
panic("cannot CacheWrapWithTrace a GasKVStore")
}
func (gs *Store)
iterator(start, end []byte, ascending bool)
types.Iterator {
var parent types.Iterator
if ascending {
parent = gs.parent.Iterator(start, end)
}
else {
parent = gs.parent.ReverseIterator(start, end)
}
gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent)
gi.(*gasIterator).consumeSeekGas()
return gi
}
type gasIterator struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
parent types.Iterator
}
func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator)
types.Iterator {
return &gasIterator{
gasMeter: gasMeter,
gasConfig: gasConfig,
parent: parent,
}
}
/ Implements Iterator.
func (gi *gasIterator)
Domain() (start, end []byte) {
return gi.parent.Domain()
}
/ Implements Iterator.
func (gi *gasIterator)
Valid()
bool {
return gi.parent.Valid()
}
/ Next implements the Iterator interface. It seeks to the next key/value pair
/ in the iterator. It incurs a flat gas cost for seeking and a variable gas
/ cost based on the current value's length if the iterator is valid.
func (gi *gasIterator)
Next() {
gi.consumeSeekGas()
gi.parent.Next()
}
/ Key implements the Iterator interface. It returns the current key and it does
/ not incur any gas cost.
func (gi *gasIterator)
Key() (key []byte) {
key = gi.parent.Key()
return key
}
/ Value implements the Iterator interface. It returns the current value and it
/ does not incur any gas cost.
func (gi *gasIterator)
Value() (value []byte) {
value = gi.parent.Value()
return value
}
/ Implements Iterator.
func (gi *gasIterator)
Close()
error {
return gi.parent.Close()
}
/ Error delegates the Error call to the parent iterator.
func (gi *gasIterator)
Error()
error {
return gi.parent.Error()
}
/ consumeSeekGas consumes on each iteration step a flat gas cost and a variable gas cost
/ based on the current value's length.
func (gi *gasIterator)
consumeSeekGas() {
if gi.Valid() {
key := gi.Key()
value := gi.Value()
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasValuePerByteDesc)
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc)
}
gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc)
}
KVStore
are called, GasKv.Store
automatically consumes appropriate amount of gas depending on the Store.gasConfig
:
Copy
Ask AI
package types
import (
"fmt"
"math"
)
/ Gas consumption descriptors.
const (
GasIterNextCostFlatDesc = "IterNextFlat"
GasValuePerByteDesc = "ValuePerByte"
GasWritePerByteDesc = "WritePerByte"
GasReadPerByteDesc = "ReadPerByte"
GasWriteCostFlatDesc = "WriteFlat"
GasReadCostFlatDesc = "ReadFlat"
GasHasDesc = "Has"
GasDeleteDesc = "Delete"
)
/ Gas measured by the SDK
type Gas = uint64
/ ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
/ negative gas consumed amount.
type ErrorNegativeGasConsumed struct {
Descriptor string
}
/ ErrorOutOfGas defines an error thrown when an action results in out of gas.
type ErrorOutOfGas struct {
Descriptor string
}
/ ErrorGasOverflow defines an error thrown when an action results gas consumption
/ unsigned integer overflow.
type ErrorGasOverflow struct {
Descriptor string
}
/ GasMeter interface to track gas consumption
type GasMeter interface {
GasConsumed()
Gas
GasConsumedToLimit()
Gas
GasRemaining()
Gas
Limit()
Gas
ConsumeGas(amount Gas, descriptor string)
RefundGas(amount Gas, descriptor string)
IsPastLimit()
bool
IsOutOfGas()
bool
String()
string
}
type basicGasMeter struct {
limit Gas
consumed Gas
}
/ NewGasMeter returns a reference to a new basicGasMeter.
func NewGasMeter(limit Gas)
GasMeter {
return &basicGasMeter{
limit: limit,
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *basicGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasRemaining returns the gas left in the GasMeter.
func (g *basicGasMeter)
GasRemaining()
Gas {
if g.IsPastLimit() {
return 0
}
return g.limit - g.consumed
}
/ Limit returns the gas limit of the GasMeter.
func (g *basicGasMeter)
Limit()
Gas {
return g.limit
}
/ GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
/ otherwise it returns the consumed gas.
/
/ NOTE: This behavior is only called when recovering from panic when
/ BlockGasMeter consumes gas past the limit.
func (g *basicGasMeter)
GasConsumedToLimit()
Gas {
if g.IsPastLimit() {
return g.limit
}
return g.consumed
}
/ addUint64Overflow performs the addition operation on two uint64 integers and
/ returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {
if math.MaxUint64-a < b {
return 0, true
}
return a + b, false
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit or out of gas.
func (g *basicGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
g.consumed = math.MaxUint64
panic(ErrorGasOverflow{
descriptor
})
}
if g.consumed > g.limit {
panic(ErrorOutOfGas{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the transaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *basicGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns true if gas consumed is past limit, otherwise it returns false.
func (g *basicGasMeter)
IsPastLimit()
bool {
return g.consumed > g.limit
}
/ IsOutOfGas returns true if gas consumed is greater than or equal to gas limit, otherwise it returns false.
func (g *basicGasMeter)
IsOutOfGas()
bool {
return g.consumed >= g.limit
}
/ String returns the BasicGasMeter's gas limit and gas consumed.
func (g *basicGasMeter)
String()
string {
return fmt.Sprintf("BasicGasMeter:\n limit: %d\n consumed: %d", g.limit, g.consumed)
}
type infiniteGasMeter struct {
consumed Gas
}
/ NewInfiniteGasMeter returns a new gas meter without a limit.
func NewInfiniteGasMeter()
GasMeter {
return &infiniteGasMeter{
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *infiniteGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasConsumedToLimit returns the gas consumed from the GasMeter since the gas is not confined to a limit.
/ NOTE: This behavior is only called when recovering from panic when BlockGasMeter consumes gas past the limit.
func (g *infiniteGasMeter)
GasConsumedToLimit()
Gas {
return g.consumed
}
/ GasRemaining returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
GasRemaining()
Gas {
return math.MaxUint64
}
/ Limit returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
Limit()
Gas {
return math.MaxUint64
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit.
func (g *infiniteGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
/ TODO: Should we set the consumed field after overflow checking?
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
panic(ErrorGasOverflow{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsPastLimit()
bool {
return false
}
/ IsOutOfGas returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsOutOfGas()
bool {
return false
}
/ String returns the InfiniteGasMeter's gas consumed.
func (g *infiniteGasMeter)
String()
string {
return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed)
}
/ 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
}
/ 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,
}
}
/ TransientGasConfig returns a default gas config for TransientStores.
func TransientGasConfig()
GasConfig {
return GasConfig{
HasCost: 100,
DeleteCost: 100,
ReadCostFlat: 100,
ReadCostPerByte: 0,
WriteCostFlat: 200,
WriteCostPerByte: 3,
IterNextCostFlat: 3,
}
}
KVStores
are wrapped in GasKv.Stores
when retrieved. This is done in the KVStore()
method of the context
:
Copy
Ask AI
package types
import (
"context"
"time"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"cosmossdk.io/core/comet"
"cosmossdk.io/core/header"
"cosmossdk.io/log"
"cosmossdk.io/store/gaskv"
storetypes "cosmossdk.io/store/types"
)
/ ExecMode defines the execution mode which can be set on a Context.
type ExecMode uint8
/ All possible execution modes.
const (
ExecModeCheck ExecMode = iota
ExecModeReCheck
ExecModeSimulate
ExecModePrepareProposal
ExecModeProcessProposal
ExecModeVoteExtension
ExecModeVerifyVoteExtension
ExecModeFinalize
)
/*
Context is an immutable object contains all information needed to
process a request.
It contains a context.Context object inside if you want to use that,
but please do not over-use it. We try to keep all data structured
and standard additions here would be better just to add to the Context struct
*/
type Context struct {
baseCtx context.Context
ms storetypes.MultiStore
/ Deprecated: Use HeaderService for height, time, and chainID and CometService for the rest
header cmtproto.Header
/ Deprecated: Use HeaderService for hash
headerHash []byte
/ Deprecated: Use HeaderService for chainID and CometService for the rest
chainID string
txBytes []byte
logger log.Logger
voteInfo []abci.VoteInfo
gasMeter storetypes.GasMeter
blockGasMeter storetypes.GasMeter
checkTx bool
recheckTx bool / if recheckTx == true, then checkTx must also be true
sigverifyTx bool / when run simulation, because the private key corresponding to the account in the genesis.json randomly generated, we must skip the sigverify.
execMode ExecMode
minGasPrice DecCoins
consParams cmtproto.ConsensusParams
eventManager EventManagerI
priority int64 / The tx priority, only relevant in CheckTx
kvGasConfig storetypes.GasConfig
transientKVGasConfig storetypes.GasConfig
streamingManager storetypes.StreamingManager
cometInfo comet.BlockInfo
headerInfo header.Info
}
/ Proposed rename, not done to avoid API breakage
type Request = Context
/ Read-only accessors
func (c Context)
Context()
context.Context {
return c.baseCtx
}
func (c Context)
MultiStore()
storetypes.MultiStore {
return c.ms
}
func (c Context)
BlockHeight()
int64 {
return c.header.Height
}
func (c Context)
BlockTime()
time.Time {
return c.header.Time
}
func (c Context)
ChainID()
string {
return c.chainID
}
func (c Context)
TxBytes() []byte {
return c.txBytes
}
func (c Context)
Logger()
log.Logger {
return c.logger
}
func (c Context)
VoteInfos() []abci.VoteInfo {
return c.voteInfo
}
func (c Context)
GasMeter()
storetypes.GasMeter {
return c.gasMeter
}
func (c Context)
BlockGasMeter()
storetypes.GasMeter {
return c.blockGasMeter
}
func (c Context)
IsCheckTx()
bool {
return c.checkTx
}
func (c Context)
IsReCheckTx()
bool {
return c.recheckTx
}
func (c Context)
IsSigverifyTx()
bool {
return c.sigverifyTx
}
func (c Context)
ExecMode()
ExecMode {
return c.execMode
}
func (c Context)
MinGasPrices()
DecCoins {
return c.minGasPrice
}
func (c Context)
EventManager()
EventManagerI {
return c.eventManager
}
func (c Context)
Priority()
int64 {
return c.priority
}
func (c Context)
KVGasConfig()
storetypes.GasConfig {
return c.kvGasConfig
}
func (c Context)
TransientKVGasConfig()
storetypes.GasConfig {
return c.transientKVGasConfig
}
func (c Context)
StreamingManager()
storetypes.StreamingManager {
return c.streamingManager
}
func (c Context)
CometInfo()
comet.BlockInfo {
return c.cometInfo
}
func (c Context)
HeaderInfo()
header.Info {
return c.headerInfo
}
/ BlockHeader returns the header by value.
func (c Context)
BlockHeader()
cmtproto.Header {
return c.header
}
/ HeaderHash returns a copy of the header hash obtained during abci.RequestBeginBlock
func (c Context)
HeaderHash() []byte {
hash := make([]byte, len(c.headerHash))
copy(hash, c.headerHash)
return hash
}
func (c Context)
ConsensusParams()
cmtproto.ConsensusParams {
return c.consParams
}
func (c Context)
Deadline() (deadline time.Time, ok bool) {
return c.baseCtx.Deadline()
}
func (c Context)
Done() <-chan struct{
} {
return c.baseCtx.Done()
}
func (c Context)
Err()
error {
return c.baseCtx.Err()
}
/ create a new context
func NewContext(ms storetypes.MultiStore, header cmtproto.Header, isCheckTx bool, logger log.Logger)
Context {
/ https://github.com/gogo/protobuf/issues/519
header.Time = header.Time.UTC()
return Context{
baseCtx: context.Background(),
ms: ms,
header: header,
chainID: header.ChainID,
checkTx: isCheckTx,
sigverifyTx: true,
logger: logger,
gasMeter: storetypes.NewInfiniteGasMeter(),
minGasPrice: DecCoins{
},
eventManager: NewEventManager(),
kvGasConfig: storetypes.KVGasConfig(),
transientKVGasConfig: storetypes.TransientGasConfig(),
}
}
/ WithContext returns a Context with an updated context.Context.
func (c Context)
WithContext(ctx context.Context)
Context {
c.baseCtx = ctx
return c
}
/ WithMultiStore returns a Context with an updated MultiStore.
func (c Context)
WithMultiStore(ms storetypes.MultiStore)
Context {
c.ms = ms
return c
}
/ WithBlockHeader returns a Context with an updated CometBFT block header in UTC time.
func (c Context)
WithBlockHeader(header cmtproto.Header)
Context {
/ https://github.com/gogo/protobuf/issues/519
header.Time = header.Time.UTC()
c.header = header
return c
}
/ WithHeaderHash returns a Context with an updated CometBFT block header hash.
func (c Context)
WithHeaderHash(hash []byte)
Context {
temp := make([]byte, len(hash))
copy(temp, hash)
c.headerHash = temp
return c
}
/ WithBlockTime returns a Context with an updated CometBFT block header time in UTC with no monotonic component.
/ Stripping the monotonic component is for time equality.
func (c Context)
WithBlockTime(newTime time.Time)
Context {
newHeader := c.BlockHeader()
/ https://github.com/gogo/protobuf/issues/519
newHeader.Time = newTime.Round(0).UTC()
return c.WithBlockHeader(newHeader)
}
/ WithProposer returns a Context with an updated proposer consensus address.
func (c Context)
WithProposer(addr ConsAddress)
Context {
newHeader := c.BlockHeader()
newHeader.ProposerAddress = addr.Bytes()
return c.WithBlockHeader(newHeader)
}
/ WithBlockHeight returns a Context with an updated block height.
func (c Context)
WithBlockHeight(height int64)
Context {
newHeader := c.BlockHeader()
newHeader.Height = height
return c.WithBlockHeader(newHeader)
}
/ WithChainID returns a Context with an updated chain identifier.
func (c Context)
WithChainID(chainID string)
Context {
c.chainID = chainID
return c
}
/ WithTxBytes returns a Context with an updated txBytes.
func (c Context)
WithTxBytes(txBytes []byte)
Context {
c.txBytes = txBytes
return c
}
/ WithLogger returns a Context with an updated logger.
func (c Context)
WithLogger(logger log.Logger)
Context {
c.logger = logger
return c
}
/ WithVoteInfos returns a Context with an updated consensus VoteInfo.
func (c Context)
WithVoteInfos(voteInfo []abci.VoteInfo)
Context {
c.voteInfo = voteInfo
return c
}
/ WithGasMeter returns a Context with an updated transaction GasMeter.
func (c Context)
WithGasMeter(meter storetypes.GasMeter)
Context {
c.gasMeter = meter
return c
}
/ WithBlockGasMeter returns a Context with an updated block GasMeter
func (c Context)
WithBlockGasMeter(meter storetypes.GasMeter)
Context {
c.blockGasMeter = meter
return c
}
/ WithKVGasConfig returns a Context with an updated gas configuration for
/ the KVStore
func (c Context)
WithKVGasConfig(gasConfig storetypes.GasConfig)
Context {
c.kvGasConfig = gasConfig
return c
}
/ WithTransientKVGasConfig returns a Context with an updated gas configuration for
/ the transient KVStore
func (c Context)
WithTransientKVGasConfig(gasConfig storetypes.GasConfig)
Context {
c.transientKVGasConfig = gasConfig
return c
}
/ WithIsCheckTx enables or disables CheckTx value for verifying transactions and returns an updated Context
func (c Context)
WithIsCheckTx(isCheckTx bool)
Context {
c.checkTx = isCheckTx
c.execMode = ExecModeCheck
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
c.execMode = ExecModeReCheck
return c
}
/ WithIsSigverifyTx called with true will sigverify in auth module
func (c Context)
WithIsSigverifyTx(isSigverifyTx bool)
Context {
c.sigverifyTx = isSigverifyTx
return c
}
/ WithExecMode returns a Context with an updated ExecMode.
func (c Context)
WithExecMode(m ExecMode)
Context {
c.execMode = m
return c
}
/ WithMinGasPrices returns a Context with an updated minimum gas price value
func (c Context)
WithMinGasPrices(gasPrices DecCoins)
Context {
c.minGasPrice = gasPrices
return c
}
/ WithConsensusParams returns a Context with an updated consensus params
func (c Context)
WithConsensusParams(params cmtproto.ConsensusParams)
Context {
c.consParams = params
return c
}
/ WithEventManager returns a Context with an updated event manager
func (c Context)
WithEventManager(em EventManagerI)
Context {
c.eventManager = em
return c
}
/ WithPriority returns a Context with an updated tx priority
func (c Context)
WithPriority(p int64)
Context {
c.priority = p
return c
}
/ WithStreamingManager returns a Context with an updated streaming manager
func (c Context)
WithStreamingManager(sm storetypes.StreamingManager)
Context {
c.streamingManager = sm
return c
}
/ WithCometInfo returns a Context with an updated comet info
func (c Context)
WithCometInfo(cometInfo comet.BlockInfo)
Context {
c.cometInfo = cometInfo
return c
}
/ WithHeaderInfo returns a Context with an updated header info
func (c Context)
WithHeaderInfo(headerInfo header.Info)
Context {
/ Settime to UTC
headerInfo.Time = headerInfo.Time.UTC()
c.headerInfo = headerInfo
return c
}
/ TODO: remove???
func (c Context)
IsZero()
bool {
return c.ms == nil
}
func (c Context)
WithValue(key, value any)
Context {
c.baseCtx = context.WithValue(c.baseCtx, key, value)
return c
}
func (c Context)
Value(key any)
any {
if key == SdkContextKey {
return c
}
return c.baseCtx.Value(key)
}
/ ----------------------------------------------------------------------------
/ Store / Caching
/ ----------------------------------------------------------------------------
/ KVStore fetches a KVStore from the MultiStore.
func (c Context)
KVStore(key storetypes.StoreKey)
storetypes.KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.kvGasConfig)
}
/ TransientStore fetches a TransientStore from the MultiStore.
func (c Context)
TransientStore(key storetypes.StoreKey)
storetypes.KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.transientKVGasConfig)
}
/ CacheContext returns a new Context with the multi-store cached and a new
/ EventManager. The cached context is written to the context when writeCache
/ is called. Note, events are automatically emitted on the parent context's
/ EventManager when the caller executes the write.
func (c Context)
CacheContext() (cc Context, writeCache func()) {
cms := c.ms.CacheMultiStore()
cc = c.WithMultiStore(cms).WithEventManager(NewEventManager())
writeCache = func() {
c.EventManager().EmitEvents(cc.EventManager().Events())
cms.Write()
}
return cc, writeCache
}
var (
_ context.Context = Context{
}
_ storetypes.Context = Context{
}
)
/ ContextKey defines a type alias for a stdlib Context key.
type ContextKey string
/ SdkContextKey is the key in the context.Context which holds the sdk.Context.
const SdkContextKey ContextKey = "sdk-context"
/ WrapSDKContext returns a stdlib context.Context with the provided sdk.Context's internal
/ context as a value. It is useful for passing an sdk.Context through methods that take a
/ stdlib context.Context parameter such as generated gRPC methods. To get the original
/ sdk.Context back, call UnwrapSDKContext.
/
/ Deprecated: there is no need to wrap anymore as the Cosmos SDK context implements context.Context.
func WrapSDKContext(ctx Context)
context.Context {
return ctx
}
/ UnwrapSDKContext retrieves a Context from a context.Context instance
/ attached with WrapSDKContext. It panics if a Context was not properly
/ attached
func UnwrapSDKContext(ctx context.Context)
Context {
if sdkCtx, ok := ctx.(Context); ok {
return sdkCtx
}
return ctx.Value(SdkContextKey).(Context)
}
context
is used. The gas configuration can be set using the WithKVGasConfig
method of the context
.
Otherwise it uses the following default:
Copy
Ask AI
package types
import (
"fmt"
"math"
)
/ Gas consumption descriptors.
const (
GasIterNextCostFlatDesc = "IterNextFlat"
GasValuePerByteDesc = "ValuePerByte"
GasWritePerByteDesc = "WritePerByte"
GasReadPerByteDesc = "ReadPerByte"
GasWriteCostFlatDesc = "WriteFlat"
GasReadCostFlatDesc = "ReadFlat"
GasHasDesc = "Has"
GasDeleteDesc = "Delete"
)
/ Gas measured by the SDK
type Gas = uint64
/ ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
/ negative gas consumed amount.
type ErrorNegativeGasConsumed struct {
Descriptor string
}
/ ErrorOutOfGas defines an error thrown when an action results in out of gas.
type ErrorOutOfGas struct {
Descriptor string
}
/ ErrorGasOverflow defines an error thrown when an action results gas consumption
/ unsigned integer overflow.
type ErrorGasOverflow struct {
Descriptor string
}
/ GasMeter interface to track gas consumption
type GasMeter interface {
GasConsumed()
Gas
GasConsumedToLimit()
Gas
GasRemaining()
Gas
Limit()
Gas
ConsumeGas(amount Gas, descriptor string)
RefundGas(amount Gas, descriptor string)
IsPastLimit()
bool
IsOutOfGas()
bool
String()
string
}
type basicGasMeter struct {
limit Gas
consumed Gas
}
/ NewGasMeter returns a reference to a new basicGasMeter.
func NewGasMeter(limit Gas)
GasMeter {
return &basicGasMeter{
limit: limit,
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *basicGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasRemaining returns the gas left in the GasMeter.
func (g *basicGasMeter)
GasRemaining()
Gas {
if g.IsPastLimit() {
return 0
}
return g.limit - g.consumed
}
/ Limit returns the gas limit of the GasMeter.
func (g *basicGasMeter)
Limit()
Gas {
return g.limit
}
/ GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
/ otherwise it returns the consumed gas.
/
/ NOTE: This behavior is only called when recovering from panic when
/ BlockGasMeter consumes gas past the limit.
func (g *basicGasMeter)
GasConsumedToLimit()
Gas {
if g.IsPastLimit() {
return g.limit
}
return g.consumed
}
/ addUint64Overflow performs the addition operation on two uint64 integers and
/ returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {
if math.MaxUint64-a < b {
return 0, true
}
return a + b, false
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit or out of gas.
func (g *basicGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
g.consumed = math.MaxUint64
panic(ErrorGasOverflow{
descriptor
})
}
if g.consumed > g.limit {
panic(ErrorOutOfGas{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the transaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *basicGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns true if gas consumed is past limit, otherwise it returns false.
func (g *basicGasMeter)
IsPastLimit()
bool {
return g.consumed > g.limit
}
/ IsOutOfGas returns true if gas consumed is greater than or equal to gas limit, otherwise it returns false.
func (g *basicGasMeter)
IsOutOfGas()
bool {
return g.consumed >= g.limit
}
/ String returns the BasicGasMeter's gas limit and gas consumed.
func (g *basicGasMeter)
String()
string {
return fmt.Sprintf("BasicGasMeter:\n limit: %d\n consumed: %d", g.limit, g.consumed)
}
type infiniteGasMeter struct {
consumed Gas
}
/ NewInfiniteGasMeter returns a new gas meter without a limit.
func NewInfiniteGasMeter()
GasMeter {
return &infiniteGasMeter{
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *infiniteGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasConsumedToLimit returns the gas consumed from the GasMeter since the gas is not confined to a limit.
/ NOTE: This behavior is only called when recovering from panic when BlockGasMeter consumes gas past the limit.
func (g *infiniteGasMeter)
GasConsumedToLimit()
Gas {
return g.consumed
}
/ GasRemaining returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
GasRemaining()
Gas {
return math.MaxUint64
}
/ Limit returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
Limit()
Gas {
return math.MaxUint64
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit.
func (g *infiniteGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
/ TODO: Should we set the consumed field after overflow checking?
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
panic(ErrorGasOverflow{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsPastLimit()
bool {
return false
}
/ IsOutOfGas returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsOutOfGas()
bool {
return false
}
/ String returns the InfiniteGasMeter's gas consumed.
func (g *infiniteGasMeter)
String()
string {
return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed)
}
/ 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
}
/ 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,
}
}
/ TransientGasConfig returns a default gas config for TransientStores.
func TransientGasConfig()
GasConfig {
return GasConfig{
HasCost: 100,
DeleteCost: 100,
ReadCostFlat: 100,
ReadCostPerByte: 0,
WriteCostFlat: 200,
WriteCostPerByte: 3,
IterNextCostFlat: 3,
}
}
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
Ask AI
package tracekv
import (
"encoding/base64"
"encoding/json"
"io"
"cosmossdk.io/errors"
"cosmossdk.io/store/types"
)
const (
writeOp operation = "write"
readOp operation = "read"
deleteOp operation = "delete"
iterKeyOp operation = "iterKey"
iterValueOp operation = "iterValue"
)
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"`
}
)
/ NewStore returns a reference to a new traceKVStore given a parent
/ KVStore implementation and a buffered writer.
func NewStore(parent types.KVStore, writer io.Writer, tc types.TraceContext) *Store {
return &Store{
parent: parent, writer: writer, context: tc
}
}
/ Get implements the KVStore interface. It traces a read operation and
/ delegates a Get call to the parent KVStore.
func (tkv *Store)
Get(key []byte) []byte {
value := tkv.parent.Get(key)
writeOperation(tkv.writer, readOp, tkv.context, key, value)
return value
}
/ Set implements the KVStore interface. It traces a write operation and
/ delegates the Set call to the parent KVStore.
func (tkv *Store)
Set(key, value []byte) {
types.AssertValidKey(key)
writeOperation(tkv.writer, writeOp, tkv.context, key, value)
tkv.parent.Set(key, value)
}
/ Delete implements the KVStore interface. It traces a write operation and
/ delegates the Delete call to the parent KVStore.
func (tkv *Store)
Delete(key []byte) {
writeOperation(tkv.writer, deleteOp, tkv.context, key, nil)
tkv.parent.Delete(key)
}
/ Has implements the KVStore interface. It delegates the Has call to the
/ parent KVStore.
func (tkv *Store)
Has(key []byte)
bool {
return tkv.parent.Has(key)
}
/ Iterator implements the KVStore interface. It delegates the Iterator call
/ to the parent KVStore.
func (tkv *Store)
Iterator(start, end []byte)
types.Iterator {
return tkv.iterator(start, end, true)
}
/ ReverseIterator implements the KVStore interface. It delegates the
/ ReverseIterator call to the parent KVStore.
func (tkv *Store)
ReverseIterator(start, end []byte)
types.Iterator {
return tkv.iterator(start, end, false)
}
/ iterator facilitates iteration over a KVStore. It delegates the necessary
/ calls to it's parent KVStore.
func (tkv *Store)
iterator(start, end []byte, ascending bool)
types.Iterator {
var parent types.Iterator
if ascending {
parent = tkv.parent.Iterator(start, end)
}
else {
parent = tkv.parent.ReverseIterator(start, end)
}
return newTraceIterator(tkv.writer, parent, tkv.context)
}
type traceIterator struct {
parent types.Iterator
writer io.Writer
context types.TraceContext
}
func newTraceIterator(w io.Writer, parent types.Iterator, tc types.TraceContext)
types.Iterator {
return &traceIterator{
writer: w, parent: parent, context: tc
}
}
/ Domain implements the Iterator interface.
func (ti *traceIterator)
Domain() (start, end []byte) {
return ti.parent.Domain()
}
/ Valid implements the Iterator interface.
func (ti *traceIterator)
Valid()
bool {
return ti.parent.Valid()
}
/ Next implements the Iterator interface.
func (ti *traceIterator)
Next() {
ti.parent.Next()
}
/ Key implements the Iterator interface.
func (ti *traceIterator)
Key() []byte {
key := ti.parent.Key()
writeOperation(ti.writer, iterKeyOp, ti.context, key, nil)
return key
}
/ Value implements the Iterator interface.
func (ti *traceIterator)
Value() []byte {
value := ti.parent.Value()
writeOperation(ti.writer, iterValueOp, ti.context, nil, value)
return value
}
/ Close implements the Iterator interface.
func (ti *traceIterator)
Close()
error {
return ti.parent.Close()
}
/ Error delegates the Error call to the parent iterator.
func (ti *traceIterator)
Error()
error {
return ti.parent.Error()
}
/ GetStoreType implements the KVStore interface. It returns the underlying
/ KVStore type.
func (tkv *Store)
GetStoreType()
types.StoreType {
return tkv.parent.GetStoreType()
}
/ CacheWrap implements the KVStore interface. It panics because a Store
/ cannot be branched.
func (tkv *Store)
CacheWrap()
types.CacheWrap {
panic("cannot CacheWrap a TraceKVStore")
}
/ CacheWrapWithTrace implements the KVStore interface. It panics as a
/ Store cannot be branched.
func (tkv *Store)
CacheWrapWithTrace(_ io.Writer, _ types.TraceContext)
types.CacheWrap {
panic("cannot CacheWrapWithTrace a TraceKVStore")
}
/ writeOperation writes a KVStore operation to the underlying io.Writer as
/ JSON-encoded data where the key/value pair is base64 encoded.
func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value []byte) {
traceOp := traceOperation{
Operation: op,
Key: base64.StdEncoding.EncodeToString(key),
Value: base64.StdEncoding.EncodeToString(value),
}
if tc != nil {
traceOp.Metadata = tc
}
raw, err := json.Marshal(traceOp)
if err != nil {
panic(errors.Wrap(err, "failed to serialize trace operation"))
}
if _, err := w.Write(raw); err != nil {
panic(errors.Wrap(err, "failed to write trace operation"))
}
_, err = io.WriteString(w, "\n")
if err != nil {
panic(errors.Wrap(err, "failed to write newline"))
}
}
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
Ask AI
package prefix
import (
"bytes"
"errors"
"io"
"cosmossdk.io/store/cachekv"
"cosmossdk.io/store/tracekv"
"cosmossdk.io/store/types"
)
var _ types.KVStore = Store{
}
/ Store is similar with cometbft/cometbft/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
}
func NewStore(parent types.KVStore, prefix []byte)
Store {
return Store{
parent: parent,
prefix: prefix,
}
}
func cloneAppend(bz, tail []byte) (res []byte) {
res = make([]byte, len(bz)+len(tail))
copy(res, bz)
copy(res[len(bz):], tail)
return
}
func (s Store)
key(key []byte) (res []byte) {
if key == nil {
panic("nil key on Store")
}
res = cloneAppend(s.prefix, key)
return
}
/ Implements Store
func (s Store)
GetStoreType()
types.StoreType {
return s.parent.GetStoreType()
}
/ Implements CacheWrap
func (s Store)
CacheWrap()
types.CacheWrap {
return cachekv.NewStore(s)
}
/ CacheWrapWithTrace implements the KVStore interface.
func (s Store)
CacheWrapWithTrace(w io.Writer, tc types.TraceContext)
types.CacheWrap {
return cachekv.NewStore(tracekv.NewStore(s, w, tc))
}
/ Implements KVStore
func (s Store)
Get(key []byte) []byte {
res := s.parent.Get(s.key(key))
return res
}
/ Implements KVStore
func (s Store)
Has(key []byte)
bool {
return s.parent.Has(s.key(key))
}
/ Implements KVStore
func (s Store)
Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
s.parent.Set(s.key(key), value)
}
/ Implements KVStore
func (s Store)
Delete(key []byte) {
s.parent.Delete(s.key(key))
}
/ Implements KVStore
/ Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L106
func (s Store)
Iterator(start, end []byte)
types.Iterator {
newstart := cloneAppend(s.prefix, start)
var newend []byte
if end == nil {
newend = cpIncr(s.prefix)
}
else {
newend = cloneAppend(s.prefix, end)
}
iter := s.parent.Iterator(newstart, newend)
return newPrefixIterator(s.prefix, start, end, iter)
}
/ ReverseIterator implements KVStore
/ Check https://github.com/cometbft/cometbft/blob/master/libs/db/prefix_db.go#L129
func (s Store)
ReverseIterator(start, end []byte)
types.Iterator {
newstart := cloneAppend(s.prefix, start)
var newend []byte
if end == nil {
newend = cpIncr(s.prefix)
}
else {
newend = cloneAppend(s.prefix, end)
}
iter := s.parent.ReverseIterator(newstart, newend)
return newPrefixIterator(s.prefix, start, end, iter)
}
var _ types.Iterator = (*prefixIterator)(nil)
type prefixIterator struct {
prefix []byte
start []byte
end []byte
iter types.Iterator
valid bool
}
func newPrefixIterator(prefix, start, end []byte, parent types.Iterator) *prefixIterator {
return &prefixIterator{
prefix: prefix,
start: start,
end: end,
iter: parent,
valid: parent.Valid() && bytes.HasPrefix(parent.Key(), prefix),
}
}
/ Implements Iterator
func (pi *prefixIterator)
Domain() ([]byte, []byte) {
return pi.start, pi.end
}
/ Implements Iterator
func (pi *prefixIterator)
Valid()
bool {
return pi.valid && pi.iter.Valid()
}
/ Implements Iterator
func (pi *prefixIterator)
Next() {
if !pi.valid {
panic("prefixIterator invalid, cannot call Next()")
}
if pi.iter.Next(); !pi.iter.Valid() || !bytes.HasPrefix(pi.iter.Key(), pi.prefix) {
/ TODO: shouldn't pi be set to nil instead?
pi.valid = false
}
}
/ Implements Iterator
func (pi *prefixIterator)
Key() (key []byte) {
if !pi.valid {
panic("prefixIterator invalid, cannot call Key()")
}
key = pi.iter.Key()
key = stripPrefix(key, pi.prefix)
return
}
/ Implements Iterator
func (pi *prefixIterator)
Value() []byte {
if !pi.valid {
panic("prefixIterator invalid, cannot call Value()")
}
return pi.iter.Value()
}
/ Implements Iterator
func (pi *prefixIterator)
Close()
error {
return pi.iter.Close()
}
/ Error returns an error if the prefixIterator is invalid defined by the Valid
/ method.
func (pi *prefixIterator)
Error()
error {
if !pi.Valid() {
return errors.New("invalid prefixIterator")
}
return nil
}
/ copied from github.com/cometbft/cometbft/libs/db/prefix_db.go
func stripPrefix(key, prefix []byte) []byte {
if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) {
panic("should not happen")
}
return key[len(prefix):]
}
/ wrapping types.PrefixEndBytes
func cpIncr(bz []byte) []byte {
return types.PrefixEndBytes(bz)
}
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 if 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.
Copy
Ask AI
package listenkv
import (
"io"
"cosmossdk.io/store/types"
)
var _ types.KVStore = &Store{
}
/ 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
listener *types.MemoryListener
parentStoreKey types.StoreKey
}
/ NewStore returns a reference to a new traceKVStore given a parent
/ KVStore implementation and a buffered writer.
func NewStore(parent types.KVStore, parentStoreKey types.StoreKey, listener *types.MemoryListener) *Store {
return &Store{
parent: parent, listener: listener, parentStoreKey: parentStoreKey
}
}
/ Get implements the KVStore interface. It traces a read operation and
/ delegates a Get call to the parent KVStore.
func (s *Store)
Get(key []byte) []byte {
value := s.parent.Get(key)
return value
}
/ Set implements the KVStore interface. It traces a write operation and
/ delegates the Set call to the parent KVStore.
func (s *Store)
Set(key, value []byte) {
types.AssertValidKey(key)
s.parent.Set(key, value)
s.listener.OnWrite(s.parentStoreKey, key, value, false)
}
/ Delete implements the KVStore interface. It traces a write operation and
/ delegates the Delete call to the parent KVStore.
func (s *Store)
Delete(key []byte) {
s.parent.Delete(key)
s.listener.OnWrite(s.parentStoreKey, key, nil, true)
}
/ Has implements the KVStore interface. It delegates the Has call to the
/ parent KVStore.
func (s *Store)
Has(key []byte)
bool {
return s.parent.Has(key)
}
/ Iterator implements the KVStore interface. It delegates the Iterator call
/ the to the parent KVStore.
func (s *Store)
Iterator(start, end []byte)
types.Iterator {
return s.iterator(start, end, true)
}
/ ReverseIterator implements the KVStore interface. It delegates the
/ ReverseIterator call the to the parent KVStore.
func (s *Store)
ReverseIterator(start, end []byte)
types.Iterator {
return s.iterator(start, end, false)
}
/ iterator facilitates iteration over a KVStore. It delegates the necessary
/ calls to it's parent KVStore.
func (s *Store)
iterator(start, end []byte, ascending bool)
types.Iterator {
var parent types.Iterator
if ascending {
parent = s.parent.Iterator(start, end)
}
else {
parent = s.parent.ReverseIterator(start, end)
}
return newTraceIterator(parent, s.listener)
}
type listenIterator struct {
parent types.Iterator
listener *types.MemoryListener
}
func newTraceIterator(parent types.Iterator, listener *types.MemoryListener)
types.Iterator {
return &listenIterator{
parent: parent, listener: listener
}
}
/ Domain implements the Iterator interface.
func (li *listenIterator)
Domain() (start, end []byte) {
return li.parent.Domain()
}
/ Valid implements the Iterator interface.
func (li *listenIterator)
Valid()
bool {
return li.parent.Valid()
}
/ Next implements the Iterator interface.
func (li *listenIterator)
Next() {
li.parent.Next()
}
/ Key implements the Iterator interface.
func (li *listenIterator)
Key() []byte {
key := li.parent.Key()
return key
}
/ Value implements the Iterator interface.
func (li *listenIterator)
Value() []byte {
value := li.parent.Value()
return value
}
/ Close implements the Iterator interface.
func (li *listenIterator)
Close()
error {
return li.parent.Close()
}
/ Error delegates the Error call to the parent iterator.
func (li *listenIterator)
Error()
error {
return li.parent.Error()
}
/ GetStoreType implements the KVStore interface. It returns the underlying
/ KVStore type.
func (s *Store)
GetStoreType()
types.StoreType {
return s.parent.GetStoreType()
}
/ CacheWrap implements the KVStore interface. It panics as a Store
/ cannot be cache wrapped.
func (s *Store)
CacheWrap()
types.CacheWrap {
panic("cannot CacheWrap a ListenKVStore")
}
/ CacheWrapWithTrace implements the KVStore interface. It panics as a
/ Store cannot be cache wrapped.
func (s *Store)
CacheWrapWithTrace(_ io.Writer, _ types.TraceContext)
types.CacheWrap {
panic("cannot CacheWrapWithTrace a ListenKVStore")
}
KVStore.Set
or KVStore.Delete
methods are called, listenkv.Store
automatically writes the operations to the set of Store.listeners
.
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.