refactor(store): fetch store keys from commit store if not provided (#20801)

This commit is contained in:
Matt Kocubinski 2024-07-01 10:24:10 -05:00 committed by GitHub
parent 07a50eefe2
commit 7c0ff0774f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 188 additions and 126 deletions

View File

@ -0,0 +1,96 @@
package commitment
import (
"bytes"
"fmt"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/store/v2/internal/encoding"
"cosmossdk.io/store/v2/proof"
)
const (
commitInfoKeyFmt = "c/%d" // c/<version>
latestVersionKey = "c/latest"
)
type MetadataStore struct {
kv corestore.KVStoreWithBatch
}
func NewMetadataStore(kv corestore.KVStoreWithBatch) *MetadataStore {
return &MetadataStore{
kv: kv,
}
}
func (m *MetadataStore) GetLatestVersion() (uint64, error) {
value, err := m.kv.Get([]byte(latestVersionKey))
if err != nil {
return 0, err
}
if value == nil {
return 0, nil
}
version, _, err := encoding.DecodeUvarint(value)
if err != nil {
return 0, err
}
return version, nil
}
func (m *MetadataStore) GetCommitInfo(version uint64) (*proof.CommitInfo, error) {
key := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
value, err := m.kv.Get(key)
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
cInfo := &proof.CommitInfo{}
if err := cInfo.Unmarshal(value); err != nil {
return nil, err
}
return cInfo, nil
}
func (m *MetadataStore) flushCommitInfo(version uint64, cInfo *proof.CommitInfo) error {
// do nothing if commit info is nil, as will be the case for an empty, initializing store
if cInfo == nil {
return nil
}
batch := m.kv.NewBatch()
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
value, err := cInfo.Marshal()
if err != nil {
return err
}
if err := batch.Set(cInfoKey, value); err != nil {
return err
}
var buf bytes.Buffer
buf.Grow(encoding.EncodeUvarintSize(version))
if err := encoding.EncodeUvarint(&buf, version); err != nil {
return err
}
if err := batch.Set([]byte(latestVersionKey), buf.Bytes()); err != nil {
return err
}
if err := batch.WriteSync(); err != nil {
return err
}
return batch.Close()
}
func (m *MetadataStore) deleteCommitInfo(version uint64) error {
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
return m.kv.Delete(cInfoKey)
}

View File

@ -1,7 +1,6 @@
package commitment
import (
"bytes"
"errors"
"fmt"
"io"
@ -14,17 +13,11 @@ import (
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/internal"
"cosmossdk.io/store/v2/internal/conv"
"cosmossdk.io/store/v2/internal/encoding"
"cosmossdk.io/store/v2/proof"
"cosmossdk.io/store/v2/snapshots"
snapshotstypes "cosmossdk.io/store/v2/snapshots/types"
)
const (
commitInfoKeyFmt = "c/%d" // c/<version>
latestVersionKey = "c/latest"
)
var (
_ store.Committer = (*CommitStore)(nil)
_ snapshots.CommitSnapshotter = (*CommitStore)(nil)
@ -38,7 +31,7 @@ var (
// and trees.
type CommitStore struct {
logger log.Logger
db corestore.KVStoreWithBatch
metadata *MetadataStore
multiTrees map[string]Tree
}
@ -46,8 +39,8 @@ type CommitStore struct {
func NewCommitStore(trees map[string]Tree, db corestore.KVStoreWithBatch, logger log.Logger) (*CommitStore, error) {
return &CommitStore{
logger: logger,
db: db,
multiTrees: trees,
metadata: NewMetadataStore(db),
}, nil
}
@ -96,23 +89,6 @@ func (c *CommitStore) WorkingCommitInfo(version uint64) *proof.CommitInfo {
}
}
func (c *CommitStore) GetLatestVersion() (uint64, error) {
value, err := c.db.Get([]byte(latestVersionKey))
if err != nil {
return 0, err
}
if value == nil {
return 0, nil
}
version, _, err := encoding.DecodeUvarint(value)
if err != nil {
return 0, err
}
return version, nil
}
func (c *CommitStore) LoadVersion(targetVersion uint64) error {
// Rollback the metadata to the target version.
latestVersion, err := c.GetLatestVersion()
@ -120,17 +96,11 @@ func (c *CommitStore) LoadVersion(targetVersion uint64) error {
return err
}
if targetVersion < latestVersion {
batch := c.db.NewBatch()
defer batch.Close()
for version := latestVersion; version > targetVersion; version-- {
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
if err := batch.Delete(cInfoKey); err != nil {
if err = c.metadata.deleteCommitInfo(version); err != nil {
return err
}
}
if err := batch.WriteSync(); err != nil {
return err
}
}
for _, tree := range c.multiTrees {
@ -146,54 +116,7 @@ func (c *CommitStore) LoadVersion(targetVersion uint64) error {
cInfo = c.WorkingCommitInfo(targetVersion)
}
return c.flushCommitInfo(targetVersion, cInfo)
}
func (c *CommitStore) GetCommitInfo(version uint64) (*proof.CommitInfo, error) {
key := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
value, err := c.db.Get(key)
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
cInfo := &proof.CommitInfo{}
if err := cInfo.Unmarshal(value); err != nil {
return nil, err
}
return cInfo, nil
}
func (c *CommitStore) flushCommitInfo(version uint64, cInfo *proof.CommitInfo) error {
// do nothing if commit info is nil, as will be the case for an empty, initializing store
if cInfo == nil {
return nil
}
batch := c.db.NewBatch()
defer batch.Close()
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
value, err := cInfo.Marshal()
if err != nil {
return err
}
if err := batch.Set(cInfoKey, value); err != nil {
return err
}
var buf bytes.Buffer
buf.Grow(encoding.EncodeUvarintSize(version))
if err := encoding.EncodeUvarint(&buf, version); err != nil {
return err
}
if err := batch.Set([]byte(latestVersionKey), buf.Bytes()); err != nil {
return err
}
return batch.WriteSync()
return c.metadata.flushCommitInfo(targetVersion, cInfo)
}
func (c *CommitStore) Commit(version uint64) (*proof.CommitInfo, error) {
@ -234,7 +157,7 @@ func (c *CommitStore) Commit(version uint64) (*proof.CommitInfo, error) {
StoreInfos: storeInfos,
}
if err := c.flushCommitInfo(version, cInfo); err != nil {
if err := c.metadata.flushCommitInfo(version, cInfo); err != nil {
return nil, err
}
@ -261,7 +184,7 @@ func (c *CommitStore) GetProof(storeKey []byte, version uint64, key []byte) ([]p
if err != nil {
return nil, err
}
cInfo, err := c.GetCommitInfo(version)
cInfo, err := c.metadata.GetCommitInfo(version)
if err != nil {
return nil, err
}
@ -294,20 +217,11 @@ func (c *CommitStore) Get(storeKey []byte, version uint64, key []byte) ([]byte,
// Prune implements store.Pruner.
func (c *CommitStore) Prune(version uint64) (ferr error) {
// prune the metadata
batch := c.db.NewBatch()
defer batch.Close()
for v := version; v > 0; v-- {
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, v))
if exist, _ := c.db.Has(cInfoKey); !exist {
break
}
if err := batch.Delete(cInfoKey); err != nil {
if err := c.metadata.deleteCommitInfo(v); err != nil {
return err
}
}
if err := batch.WriteSync(); err != nil {
return err
}
for _, tree := range c.multiTrees {
if err := tree.Prune(version); err != nil {
@ -480,6 +394,14 @@ loop:
return snapshotItem, c.LoadVersion(version)
}
func (c *CommitStore) GetCommitInfo(version uint64) (*proof.CommitInfo, error) {
return c.metadata.GetCommitInfo(version)
}
func (c *CommitStore) GetLatestVersion() (uint64, error) {
return c.metadata.GetLatestVersion()
}
func (c *CommitStore) Close() (ferr error) {
for _, tree := range c.multiTrees {
if err := tree.Close(); err != nil {

View File

@ -22,8 +22,9 @@ type (
// StoreInfo defines store-specific commit information. It contains a reference
// between a store name/key and the commit ID.
StoreInfo struct {
Name []byte
CommitID CommitID
Name []byte
CommitID CommitID
Structure string
}
// CommitID defines the commitment information when a specific store is
@ -98,6 +99,7 @@ func (ci *CommitInfo) encodedSize() int {
for _, storeInfo := range ci.StoreInfos {
size += encoding.EncodeBytesSize(storeInfo.Name)
size += encoding.EncodeBytesSize(storeInfo.CommitID.Hash)
size += encoding.EncodeBytesSize([]byte(storeInfo.Structure))
}
return size
}
@ -110,6 +112,7 @@ func (ci *CommitInfo) encodedSize() int {
// - for each store:
// - store name (bytes)
// - store hash (bytes)
// - store commit structure (bytes)
func (ci *CommitInfo) Marshal() ([]byte, error) {
var buf bytes.Buffer
buf.Grow(ci.encodedSize())
@ -130,6 +133,9 @@ func (ci *CommitInfo) Marshal() ([]byte, error) {
if err := encoding.EncodeBytes(&buf, si.CommitID.Hash); err != nil {
return nil, err
}
if err := encoding.EncodeBytes(&buf, []byte(si.Structure)); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
@ -172,6 +178,14 @@ func (ci *CommitInfo) Unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
// Structure
structure, n, err := encoding.DecodeBytes(buf)
if err != nil {
return err
}
buf = buf[n:]
ci.StoreInfos[i].Structure = string(structure)
ci.StoreInfos[i].CommitID = CommitID{
Hash: hash,
Version: ci.Version,

View File

@ -1,6 +1,7 @@
package proof
import (
"fmt"
"testing"
"time"
@ -12,48 +13,59 @@ func TestGetStoreProof(t *testing.T) {
storeInfos []StoreInfo
}{
{[]StoreInfo{
{[]byte("key1"), CommitID{1, []byte("value1")}},
{[]byte("key1"), CommitID{1, []byte("value1")}, "iavl"},
}},
{[]StoreInfo{
{[]byte("key2"), CommitID{1, []byte("value2")}},
{[]byte("key1"), CommitID{1, []byte("value1")}},
{[]byte("key2"), CommitID{1, []byte("value2")}, "iavl"},
{[]byte("key1"), CommitID{1, []byte("value1")}, "iavl"},
}},
{[]StoreInfo{
{[]byte("key3"), CommitID{1, []byte("value3")}},
{[]byte("key2"), CommitID{1, []byte("value2")}},
{[]byte("key1"), CommitID{1, []byte("value1")}},
{[]byte("key3"), CommitID{1, []byte("value3")}, "iavl"},
{[]byte("key2"), CommitID{1, []byte("value2")}, "iavl"},
{[]byte("key1"), CommitID{1, []byte("value1")}, "iavl"},
}},
{[]StoreInfo{
{[]byte("key2"), CommitID{1, []byte("value2")}},
{[]byte("key1"), CommitID{1, []byte("value1")}},
{[]byte("key3"), CommitID{1, []byte("value3")}},
{[]byte("key2"), CommitID{1, []byte("value2")}, "iavl"},
{[]byte("key1"), CommitID{1, []byte("value1")}, "iavl"},
{[]byte("key3"), CommitID{1, []byte("value3")}, "iavl"},
}},
{[]StoreInfo{
{[]byte("key4"), CommitID{1, []byte("value4")}},
{[]byte("key1"), CommitID{1, []byte("value1")}},
{[]byte("key3"), CommitID{1, []byte("value3")}},
{[]byte("key2"), CommitID{1, []byte("value2")}},
{[]byte("key4"), CommitID{1, []byte("value4")}, "iavl"},
{[]byte("key1"), CommitID{1, []byte("value1")}, "iavl"},
{[]byte("key3"), CommitID{1, []byte("value3")}, "iavl"},
{[]byte("key2"), CommitID{1, []byte("value2")}, "iavl"},
}},
}
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, []byte("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)
}
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
// 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, []byte("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)
bz, err := ci.Marshal()
require.NoError(t, err)
var ci2 CommitInfo
err = ci2.Unmarshal(bz)
require.NoError(t, err)
require.True(t, ci.Timestamp.Equal(ci2.Timestamp))
ci2.Timestamp = ci.Timestamp
require.Equal(t, ci, ci2)
}
})
}
}

View File

@ -83,6 +83,24 @@ func CreateRootStore(opts *FactoryOptions) (store.RootStore, error) {
}
ss = storage.NewStorageStore(ssDb, opts.Logger)
if len(opts.StoreKeys) == 0 {
metadata := commitment.NewMetadataStore(opts.SCRawDB)
latestVersion, err := metadata.GetLatestVersion()
if err != nil {
return nil, err
}
lastCommitInfo, err := metadata.GetCommitInfo(latestVersion)
if err != nil {
return nil, err
}
if lastCommitInfo == nil {
return nil, fmt.Errorf("tried to construct a root store with no store keys specified but no commit info found for version %d", latestVersion)
}
for _, si := range lastCommitInfo.StoreInfos {
opts.StoreKeys = append(opts.StoreKeys, string(si.Name))
}
}
trees := make(map[string]commitment.Tree)
for _, key := range opts.StoreKeys {
if internal.IsMemoryStoreKey(key) {