Skip to content

Commit

Permalink
feat(store/v2): Implement the GetProof for multi store (#18736)
Browse files Browse the repository at this point in the history
  • Loading branch information
cool-develope authored Jan 17, 2024
1 parent d9a5b1e commit bf37398
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 77 deletions.
104 changes: 104 additions & 0 deletions store/commit_info.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package store

import (
"bytes"
"fmt"
"sort"
"time"

"cosmossdk.io/store/v2/internal/encoding"
)

type (
Expand Down Expand Up @@ -50,6 +53,16 @@ func (ci *CommitInfo) Hash() []byte {
return rootHash
}

// GetStoreCommitID returns the CommitID for the given store key.
func (ci *CommitInfo) GetStoreCommitID(storeKey string) CommitID {
for _, si := range ci.StoreInfos {
if si.Name == storeKey {
return si.CommitID
}
}
return CommitID{}
}

// GetStoreProof takes in a storeKey and returns a proof of the store key in addition
// to the root hash it should be proved against. If an empty string is provided, the first
// store based on lexographical ordering will be proved.
Expand Down Expand Up @@ -77,6 +90,97 @@ func (ci *CommitInfo) GetStoreProof(storeKey string) ([]byte, *CommitmentOp, err
return rootHash, &commitmentOp, nil
}

// encodedSize returns the encoded size of CommitInfo for preallocation in Marshal.
func (ci *CommitInfo) encodedSize() int {
size := encoding.EncodeUvarintSize(ci.Version)
size += encoding.EncodeVarintSize(ci.Timestamp.UnixNano())
size += encoding.EncodeUvarintSize(uint64(len(ci.StoreInfos)))
for _, storeInfo := range ci.StoreInfos {
size += encoding.EncodeBytesSize([]byte(storeInfo.Name))
size += encoding.EncodeBytesSize(storeInfo.CommitID.Hash)
}
return size
}

// Marshal returns the encoded byte representation of CommitInfo.
// NOTE: CommitInfo is encoded as follows:
// - version (uvarint)
// - timestamp (varint)
// - number of stores (uvarint)
// - for each store:
// - store name (bytes)
// - store hash (bytes)
func (ci *CommitInfo) Marshal() ([]byte, error) {
var buf bytes.Buffer
buf.Grow(ci.encodedSize())

if err := encoding.EncodeUvarint(&buf, ci.Version); err != nil {
return nil, err
}
if err := encoding.EncodeVarint(&buf, ci.Timestamp.UnixNano()); err != nil {
return nil, err
}
if err := encoding.EncodeUvarint(&buf, uint64(len(ci.StoreInfos))); err != nil {
return nil, err
}
for _, si := range ci.StoreInfos {
if err := encoding.EncodeBytes(&buf, []byte(si.Name)); err != nil {
return nil, err
}
if err := encoding.EncodeBytes(&buf, si.CommitID.Hash); err != nil {
return nil, err
}
}

return buf.Bytes(), nil
}

// Unmarshal unmarshals the encoded byte representation of CommitInfo.
func (ci *CommitInfo) Unmarshal(buf []byte) error {
// Version
version, n, err := encoding.DecodeUvarint(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.Version = version
// Timestamp
timestamp, n, err := encoding.DecodeVarint(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.Timestamp = time.Unix(timestamp/int64(time.Second), timestamp%int64(time.Second))
// StoreInfos
storeInfosLen, n, err := encoding.DecodeUvarint(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.StoreInfos = make([]StoreInfo, storeInfosLen)
for i := 0; i < int(storeInfosLen); i++ {
// Name
name, n, err := encoding.DecodeBytes(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.StoreInfos[i].Name = string(name)
// CommitID
hash, n, err := encoding.DecodeBytes(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.StoreInfos[i].CommitID = CommitID{
Hash: hash,
Version: ci.Version,
}
}

return nil
}

func (ci *CommitInfo) CommitID() CommitID {
return CommitID{
Version: ci.Version,
Expand Down
59 changes: 59 additions & 0 deletions store/commit_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package store

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestGetStoreProof(t *testing.T) {
tests := []struct {
storeInfos []StoreInfo
}{
{[]StoreInfo{
{"key1", CommitID{1, []byte("value1")}},
}},
{[]StoreInfo{
{"key2", CommitID{1, []byte("value2")}},
{"key1", CommitID{1, []byte("value1")}},
}},
{[]StoreInfo{
{"key3", CommitID{1, []byte("value3")}},
{"key2", CommitID{1, []byte("value2")}},
{"key1", CommitID{1, []byte("value1")}},
}},
{[]StoreInfo{
{"key2", CommitID{1, []byte("value2")}},
{"key1", CommitID{1, []byte("value1")}},
{"key3", CommitID{1, []byte("value3")}},
}},
{[]StoreInfo{
{"key4", CommitID{1, []byte("value4")}},
{"key1", CommitID{1, []byte("value1")}},
{"key3", CommitID{1, []byte("value3")}},
{"key2", CommitID{1, []byte("value2")}},
}},
}

for i, tc := range tests {
// create a commit info
ci := CommitInfo{
Version: 1,
Timestamp: time.Now(),
StoreInfos: tc.storeInfos,
}
commitHash := ci.Hash()
// make sure the store infos are sorted
require.Equal(t, ci.StoreInfos[0].Name, "key1")
for _, si := range tc.storeInfos {
// get the proof
_, proof, err := ci.GetStoreProof(si.Name)
require.NoError(t, err, "test case %d", i)
// verify the proof
expRoots, err := proof.Run([][]byte{si.CommitID.Hash})
require.NoError(t, err, "test case %d", i)
require.Equal(t, commitHash, expRoots[0], "test case %d", i)
}
}
}
17 changes: 11 additions & 6 deletions store/commitment/iavl/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ func (t *IavlTree) Set(key, value []byte) error {
return err
}

// WorkingHash returns the working hash of the database.
// Hash returns the hash of the latest saved version of the tree.
func (t *IavlTree) Hash() []byte {
return t.tree.Hash()
}

// WorkingHash returns the working hash of the tree.
func (t *IavlTree) WorkingHash() []byte {
return t.tree.WorkingHash()
}
Expand All @@ -54,10 +59,10 @@ func (t *IavlTree) LoadVersion(version uint64) error {
return t.tree.LoadVersionForOverwriting(int64(version))
}

// Commit commits the current state to the database.
func (t *IavlTree) Commit() ([]byte, error) {
hash, _, err := t.tree.SaveVersion()
return hash, err
// Commit commits the current state to the tree.
func (t *IavlTree) Commit() ([]byte, uint64, error) {
hash, v, err := t.tree.SaveVersion()
return hash, uint64(v), err
}

// GetProof returns a proof for the given key and version.
Expand All @@ -70,7 +75,7 @@ func (t *IavlTree) GetProof(version uint64, key []byte) (*ics23.CommitmentProof,
return imutableTree.GetProof(key)
}

// GetLatestVersion returns the latest version of the database.
// GetLatestVersion returns the latest version of the tree.
func (t *IavlTree) GetLatestVersion() uint64 {
return uint64(t.tree.Version())
}
Expand Down
10 changes: 6 additions & 4 deletions store/commitment/iavl/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestCommitterSuite(t *testing.T) {
prefixDB := dbm.NewPrefixDB(db, []byte(storeKey))
multiTrees[storeKey] = NewIavlTree(prefixDB, logger, cfg)
}
return commitment.NewCommitStore(multiTrees, logger)
return commitment.NewCommitStore(multiTrees, db, logger)
},
}

Expand Down Expand Up @@ -51,8 +51,9 @@ func TestIavlTree(t *testing.T) {
require.Equal(t, uint64(0), tree.GetLatestVersion())

// commit the batch
commitHash, err := tree.Commit()
commitHash, version, err := tree.Commit()
require.NoError(t, err)
require.Equal(t, version, uint64(1))
require.Equal(t, workingHash, commitHash)
require.Equal(t, uint64(1), tree.GetLatestVersion())

Expand All @@ -63,8 +64,9 @@ func TestIavlTree(t *testing.T) {
require.NoError(t, tree.Remove([]byte("key1"))) // delete key1
version2Hash := tree.WorkingHash()
require.NotNil(t, version2Hash)
commitHash, err = tree.Commit()
commitHash, version, err = tree.Commit()
require.NoError(t, err)
require.Equal(t, version, uint64(2))
require.Equal(t, version2Hash, commitHash)

// get proof for key1
Expand All @@ -80,7 +82,7 @@ func TestIavlTree(t *testing.T) {
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()
_, _, err = tree.Commit()
require.NoError(t, err)

// prune version 1
Expand Down
Loading

0 comments on commit bf37398

Please sign in to comment.