From 7c0ff0774fd4c090d22ee2691aec45a44aebf7bf Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 1 Jul 2024 10:24:10 -0500 Subject: [PATCH] refactor(store): fetch store keys from commit store if not provided (#20801) --- store/v2/commitment/metadata.go | 96 +++++++++++++++++++++++++ store/v2/commitment/store.go | 108 ++++------------------------- store/v2/proof/commit_info.go | 18 ++++- store/v2/proof/commit_info_test.go | 74 +++++++++++--------- store/v2/root/factory.go | 18 +++++ 5 files changed, 188 insertions(+), 126 deletions(-) create mode 100644 store/v2/commitment/metadata.go diff --git a/store/v2/commitment/metadata.go b/store/v2/commitment/metadata.go new file mode 100644 index 0000000000..258f32672d --- /dev/null +++ b/store/v2/commitment/metadata.go @@ -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/ + 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) +} diff --git a/store/v2/commitment/store.go b/store/v2/commitment/store.go index 355b6ff945..0dda7f2829 100644 --- a/store/v2/commitment/store.go +++ b/store/v2/commitment/store.go @@ -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/ - 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 { diff --git a/store/v2/proof/commit_info.go b/store/v2/proof/commit_info.go index 91299ddca2..d95c152b46 100644 --- a/store/v2/proof/commit_info.go +++ b/store/v2/proof/commit_info.go @@ -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, diff --git a/store/v2/proof/commit_info_test.go b/store/v2/proof/commit_info_test.go index 328f82869d..e09449c519 100644 --- a/store/v2/proof/commit_info_test.go +++ b/store/v2/proof/commit_info_test.go @@ -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) + } + }) } } diff --git a/store/v2/root/factory.go b/store/v2/root/factory.go index efe9cb7ddc..ced6ee9127 100644 --- a/store/v2/root/factory.go +++ b/store/v2/root/factory.go @@ -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) {