feat(store/v2): add the commitment store (#18526)
This commit is contained in:
@@ -8,10 +8,10 @@ import (
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
|
||||
log "cosmossdk.io/log"
|
||||
"cosmossdk.io/store/v2"
|
||||
"cosmossdk.io/store/v2/commitment"
|
||||
)
|
||||
|
||||
var _ store.Committer = (*IavlTree)(nil)
|
||||
var _ commitment.Tree = (*IavlTree)(nil)
|
||||
|
||||
// IavlTree is a wrapper around iavl.MutableTree.
|
||||
type IavlTree struct {
|
||||
@@ -26,25 +26,19 @@ func NewIavlTree(db dbm.DB, logger log.Logger, cfg *Config) *IavlTree {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteBatch writes a batch of key-value pairs to the database.
|
||||
func (t *IavlTree) WriteBatch(cs *store.Changeset) error {
|
||||
for _, kv := range cs.Pairs {
|
||||
if kv.Value == nil {
|
||||
_, res, err := t.tree.Remove(kv.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !res {
|
||||
return fmt.Errorf("failed to delete key %X", kv.Key)
|
||||
}
|
||||
} else {
|
||||
_, err := t.tree.Set(kv.Key, kv.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Remove removes the given key from the tree.
|
||||
func (t *IavlTree) Remove(key []byte) error {
|
||||
_, res, err := t.tree.Remove(key)
|
||||
if !res {
|
||||
return fmt.Errorf("key %x not found", key)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Set sets the given key-value pair in the tree.
|
||||
func (t *IavlTree) Set(key, value []byte) error {
|
||||
_, err := t.tree.Set(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// WorkingHash returns the working hash of the database.
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/store/v2"
|
||||
)
|
||||
|
||||
func generateTree(treeType string) *IavlTree {
|
||||
@@ -25,13 +24,9 @@ func TestIavlTree(t *testing.T) {
|
||||
require.Equal(t, uint64(0), initVersion)
|
||||
|
||||
// write a batch of version 1
|
||||
cs1 := store.NewChangeset()
|
||||
cs1.Add([]byte("key1"), []byte("value1"))
|
||||
cs1.Add([]byte("key2"), []byte("value2"))
|
||||
cs1.Add([]byte("key3"), []byte("value3"))
|
||||
|
||||
err := tree.WriteBatch(cs1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tree.Set([]byte("key1"), []byte("value1")))
|
||||
require.NoError(t, tree.Set([]byte("key2"), []byte("value2")))
|
||||
require.NoError(t, tree.Set([]byte("key3"), []byte("value3")))
|
||||
|
||||
workingHash := tree.WorkingHash()
|
||||
require.NotNil(t, workingHash)
|
||||
@@ -44,13 +39,10 @@ func TestIavlTree(t *testing.T) {
|
||||
require.Equal(t, uint64(1), tree.GetLatestVersion())
|
||||
|
||||
// write a batch of version 2
|
||||
cs2 := store.NewChangeset()
|
||||
cs2.Add([]byte("key4"), []byte("value4"))
|
||||
cs2.Add([]byte("key5"), []byte("value5"))
|
||||
cs2.Add([]byte("key6"), []byte("value6"))
|
||||
cs2.Add([]byte("key1"), nil) // delete key1
|
||||
err = tree.WriteBatch(cs2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tree.Set([]byte("key4"), []byte("value4")))
|
||||
require.NoError(t, tree.Set([]byte("key5"), []byte("value5")))
|
||||
require.NoError(t, tree.Set([]byte("key6"), []byte("value6")))
|
||||
require.NoError(t, tree.Remove([]byte("key1"))) // delete key1
|
||||
version2Hash := tree.WorkingHash()
|
||||
require.NotNil(t, version2Hash)
|
||||
commitHash, err = tree.Commit()
|
||||
@@ -67,10 +59,8 @@ func TestIavlTree(t *testing.T) {
|
||||
require.NotNil(t, proof.GetNonexist())
|
||||
|
||||
// write a batch of version 3
|
||||
cs3 := store.NewChangeset()
|
||||
cs3.Add([]byte("key7"), []byte("value7"))
|
||||
cs3.Add([]byte("key8"), []byte("value8"))
|
||||
err = tree.WriteBatch(cs3)
|
||||
require.NoError(t, tree.Set([]byte("key7"), []byte("value7")))
|
||||
require.NoError(t, tree.Set([]byte("key8"), []byte("value8")))
|
||||
require.NoError(t, err)
|
||||
_, err = tree.Commit()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package commitment
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/store/v2"
|
||||
)
|
||||
|
||||
var _ store.Committer = (*CommitStore)(nil)
|
||||
|
||||
// CommitStore is a wrapper around multiple Tree objects mapped by a unique store
|
||||
// key. Each store key reflects dedicated and unique usage within a module. A caller
|
||||
// can construct a CommitStore with one or more store keys. It is expected that a
|
||||
// RootStore use a CommitStore as an abstraction to handle multiple store keys
|
||||
// and trees.
|
||||
type CommitStore struct {
|
||||
logger log.Logger
|
||||
|
||||
multiTrees map[string]Tree
|
||||
}
|
||||
|
||||
// NewCommitStore creates a new CommitStore instance.
|
||||
func NewCommitStore(multiTrees map[string]Tree, logger log.Logger) (*CommitStore, error) {
|
||||
return &CommitStore{
|
||||
logger: logger,
|
||||
multiTrees: multiTrees,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CommitStore) WriteBatch(cs *store.Changeset) error {
|
||||
for storeKey, pairs := range cs.Pairs {
|
||||
tree, ok := c.multiTrees[storeKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("store key %s not found in multiTrees", storeKey)
|
||||
}
|
||||
for _, kv := range pairs {
|
||||
if kv.Value == nil {
|
||||
if err := tree.Remove(kv.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tree.Set(kv.Key, kv.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommitStore) WorkingStoreInfos(version uint64) []store.StoreInfo {
|
||||
storeInfos := make([]store.StoreInfo, 0, len(c.multiTrees))
|
||||
for storeKey, tree := range c.multiTrees {
|
||||
storeInfos = append(storeInfos, store.StoreInfo{
|
||||
Name: storeKey,
|
||||
CommitID: store.CommitID{
|
||||
Version: version,
|
||||
Hash: tree.WorkingHash(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return storeInfos
|
||||
}
|
||||
|
||||
func (c *CommitStore) GetLatestVersion() (uint64, error) {
|
||||
latestVersion := uint64(0)
|
||||
for storeKey, tree := range c.multiTrees {
|
||||
version := tree.GetLatestVersion()
|
||||
if latestVersion != 0 && version != latestVersion {
|
||||
return 0, fmt.Errorf("store %s has version %d, not equal to latest version %d", storeKey, version, latestVersion)
|
||||
}
|
||||
latestVersion = version
|
||||
}
|
||||
|
||||
return latestVersion, nil
|
||||
}
|
||||
|
||||
func (c *CommitStore) LoadVersion(targetVersion uint64) error {
|
||||
for _, tree := range c.multiTrees {
|
||||
if err := tree.LoadVersion(targetVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommitStore) Commit() ([]store.StoreInfo, error) {
|
||||
storeInfos := make([]store.StoreInfo, 0, len(c.multiTrees))
|
||||
for storeKey, tree := range c.multiTrees {
|
||||
hash, err := tree.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storeInfos = append(storeInfos, store.StoreInfo{
|
||||
Name: storeKey,
|
||||
CommitID: store.CommitID{
|
||||
Version: tree.GetLatestVersion(),
|
||||
Hash: hash,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return storeInfos, nil
|
||||
}
|
||||
|
||||
func (c *CommitStore) GetProof(storeKey string, version uint64, key []byte) (*ics23.CommitmentProof, error) {
|
||||
tree, ok := c.multiTrees[storeKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("store %s not found", storeKey)
|
||||
}
|
||||
|
||||
return tree.GetProof(version, key)
|
||||
}
|
||||
|
||||
func (c *CommitStore) Prune(version uint64) (ferr error) {
|
||||
for _, tree := range c.multiTrees {
|
||||
if err := tree.Prune(version); err != nil {
|
||||
ferr = errors.Join(ferr, err)
|
||||
}
|
||||
}
|
||||
|
||||
return ferr
|
||||
}
|
||||
|
||||
func (c *CommitStore) Close() (ferr error) {
|
||||
for _, tree := range c.multiTrees {
|
||||
if err := tree.Close(); err != nil {
|
||||
ferr = errors.Join(ferr, err)
|
||||
}
|
||||
}
|
||||
|
||||
return ferr
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package commitment
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
)
|
||||
|
||||
// Tree is the interface that wraps the basic Tree methods.
|
||||
type Tree interface {
|
||||
Set(key, value []byte) error
|
||||
Remove(key []byte) error
|
||||
GetLatestVersion() uint64
|
||||
WorkingHash() []byte
|
||||
LoadVersion(version uint64) error
|
||||
Commit() ([]byte, error)
|
||||
GetProof(version uint64, key []byte) (*ics23.CommitmentProof, error)
|
||||
Prune(version uint64) error
|
||||
|
||||
io.Closer
|
||||
}
|
||||
Reference in New Issue
Block a user