Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(store/v2): implement the feature to upgrade the store keys #20453

Merged
merged 46 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
34f301a
init
cool-develope May 25, 2024
e22fa63
Merge branch 'main' into store/upgrade
cool-develope May 29, 2024
deb1cef
wip
cool-develope May 31, 2024
422c2f1
Merge branch 'main' into store/upgrade
cool-develope May 31, 2024
de5188c
add tests
cool-develope Jun 3, 2024
787c2f1
Merge branch 'main' into store/upgrade
cool-develope Jun 3, 2024
cc5803d
lint
cool-develope Jun 3, 2024
982234c
feat(store/v2): Implement the feature to upgrade `storeKeys` on the s…
cool-develope Jun 11, 2024
52a0148
Merge branch 'main' into store/upgrade
cool-develope Jun 11, 2024
c434513
address comments
cool-develope Jun 12, 2024
01f58fe
Merge branch 'main' into store/upgrade
cool-develope Jun 12, 2024
0a395e0
error handle
cool-develope Jun 12, 2024
9b91446
Merge branch 'main' into store/upgrade
cool-develope Jun 19, 2024
fee3fd7
comments
cool-develope Jun 19, 2024
9ddef3c
Merge branch 'main' into store/upgrade
cool-develope Jun 24, 2024
cc04a80
Merge branch 'main' into store/upgrade
cool-develope Jun 27, 2024
5231c52
comments
cool-develope Jun 27, 2024
37079b9
Merge branch 'main' into store/upgrade
cool-develope Jun 27, 2024
e23e66a
comments
cool-develope Jun 28, 2024
5249bcd
Merge branch 'main' into store/upgrade
cool-develope Jul 1, 2024
b74f833
Merge branch 'main' into store/upgrade
cool-develope Jul 3, 2024
dd22733
wrap up
cool-develope Jul 3, 2024
6f23f8e
Merge branch 'main' into store/upgrade
cool-develope Jul 8, 2024
0b11f85
feat(store/v2): Removing old store keys by pruning (#20927)
cool-develope Jul 24, 2024
ad0310d
remove rename feature
cool-develope Jul 24, 2024
5e5b340
Merge branch 'main' into store/upgrade
cool-develope Jul 24, 2024
2af4c06
wip
cool-develope Jul 24, 2024
7240ced
fixing
cool-develope Jul 24, 2024
4309308
minor fix
cool-develope Jul 24, 2024
6d81e39
minor cleanup
cool-develope Jul 24, 2024
e99168d
linting
cool-develope Jul 24, 2024
77d0533
Merge branch 'main' into store/upgrade
cool-develope Jul 25, 2024
f70c4fa
Merge branch 'main' into store/upgrade
cool-develope Jul 29, 2024
2d341ae
go mod tidy
cool-develope Jul 29, 2024
6ae8def
docs
cool-develope Jul 30, 2024
805f697
Merge branch 'main' into store/upgrade
cool-develope Jul 30, 2024
d3c6e87
revert go.mod update
cool-develope Jul 30, 2024
2e1d53b
go mod update
cool-develope Jul 31, 2024
be3e5af
Merge branch 'main' into store/upgrade
cool-develope Jul 31, 2024
0948f11
iavl version match
cool-develope Aug 1, 2024
74b31e0
Merge branch 'main' into store/upgrade
cool-develope Aug 1, 2024
66d86ec
Update store/v2/README.md
cool-develope Aug 1, 2024
f50b207
go mod update
cool-develope Aug 1, 2024
bca11e4
remove mountTreeFn
cool-develope Aug 5, 2024
32fbcfe
Merge branch 'main' into store/upgrade
cool-develope Aug 5, 2024
65bce60
linting
cool-develope Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 2 additions & 25 deletions core/store/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ package store

// 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"`
Added []string `json:"added"`
Deleted []string `json:"deleted"`
}

// IsAdded returns true if the given key should be added
Expand Down Expand Up @@ -40,17 +31,3 @@ func (s *StoreUpgrades) IsDeleted(key string) bool {
}
return false
}

// 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 ""
}
52 changes: 42 additions & 10 deletions store/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ The `store` package contains the implementation of store/v2, which is the SDK's
abstraction around managing historical and committed state. See [ADR-065](../docs/architecture/adr-065-store-v2.md)
and [Store v2 Design](https://docs.google.com/document/d/1l6uXIjTPHOOWM5N4sUUmUfCZvePoa5SNfIEtmgvgQSU/edit#heading=h.nz8dqy6wa4g1) for a high-level overview of the design and rationale.

## Migration

<!-- TODO -->

## Pruning

The `root.Store` is NOT responsible for pruning. Rather, pruning is the responsibility
of the underlying SS and SC layers. This means pruning can be implementation specific,
such as being synchronous or asynchronous.

## Usage

The `store` package contains a `root.Store` type which is intended to act as an
Expand All @@ -29,3 +19,45 @@ from the perspective of `root.Store`, there is no notion of multi or single tree
rather these are implementation details of SS and SC. For SS, we utilize store keys
to namespace raw key/value pairs. For SC, we utilize an abstraction, `commitment.CommitStore`,
to map store keys to a commitment trees.

## Upgrades

The `LoadVersionAndUpgrade` API of the `root.store` allows for adding or removing
store keys. This is useful for upgrading the chain with new modules or removing
old ones. The `Rename` feature is deprecated and should not be used in store/v2.
cool-develope marked this conversation as resolved.
Show resolved Hide resolved

```mermaid
sequenceDiagram
participant S as Store
participant SS as StateStorage
participant SC as StateCommitment
alt SC is a UpgradeableStore
S->>SC: LoadVersionAndUpgrade
SC->>SC: Mount new store keys
SC->>SC: Prune removed store keys
end
SC->>S: LoadVersion Result
alt SS is a UpgradableDatabase
S->>SS: PruneStoreKeys
end
```

`Prune store keys` does not remove the data from the SC and SS instantly. It only
marks the store keys as pruned. The actual data removal is done by the pruning
process of the underlying SS and SC.
cool-develope marked this conversation as resolved.
Show resolved Hide resolved

## Migration

<!-- TODO -->
cool-develope marked this conversation as resolved.
Show resolved Hide resolved

## Pruning

The `root.Store` is NOT responsible for pruning. Rather, pruning is the responsibility
of the underlying SS and SC layers. This means pruning can be implementation specific,
such as being synchronous or asynchronous.



## Test Coverage

The test coverage of the following logical components should be over 60%:
5 changes: 3 additions & 2 deletions store/v2/commitment/iavl/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ func (t *IavlTree) Get(version uint64, key []byte) ([]byte, error) {
}

// GetLatestVersion returns the latest version of the tree.
func (t *IavlTree) GetLatestVersion() uint64 {
return uint64(t.tree.Version())
func (t *IavlTree) GetLatestVersion() (uint64, error) {
v, err := t.tree.GetLatestVersion()
return uint64(v), err
}

// SetInitialVersion sets the initial version of the database.
Expand Down
25 changes: 18 additions & 7 deletions store/v2/commitment/iavl/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ func TestCommitterSuite(t *testing.T) {
NewStore: func(db corestore.KVStoreWithBatch, storeKeys []string, logger corelog.Logger) (*commitment.CommitStore, error) {
multiTrees := make(map[string]commitment.Tree)
cfg := DefaultConfig()
for _, storeKey := range storeKeys {
mountTreeFn := func(storeKey string) (commitment.Tree, error) {
prefixDB := dbm.NewPrefixDB(db, []byte(storeKey))
multiTrees[storeKey] = NewIavlTree(prefixDB, logger, cfg)
return NewIavlTree(prefixDB, logger, cfg), nil
}
for _, storeKey := range storeKeys {
multiTrees[storeKey], _ = mountTreeFn(storeKey)
cool-develope marked this conversation as resolved.
Show resolved Hide resolved
cool-develope marked this conversation as resolved.
Show resolved Hide resolved
}
return commitment.NewCommitStore(multiTrees, db, logger)

return commitment.NewCommitStore(multiTrees, db, mountTreeFn, logger)
},
}

Expand All @@ -41,7 +45,8 @@ func TestIavlTree(t *testing.T) {
tree := generateTree()
require.NotNil(t, tree)

initVersion := tree.GetLatestVersion()
initVersion, err := tree.GetLatestVersion()
require.NoError(t, err)
require.Equal(t, uint64(0), initVersion)

// write a batch of version 1
Expand All @@ -51,14 +56,18 @@ func TestIavlTree(t *testing.T) {

workingHash := tree.WorkingHash()
require.NotNil(t, workingHash)
require.Equal(t, uint64(0), tree.GetLatestVersion())
v, err := tree.GetLatestVersion()
require.NoError(t, err)
require.Equal(t, uint64(0), v)

// commit the batch
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())
v, err = tree.GetLatestVersion()
require.NoError(t, err)
require.Equal(t, uint64(1), v)

// ensure we can get expected values
bz, err := tree.Get(1, []byte("key1"))
Expand Down Expand Up @@ -100,7 +109,9 @@ func TestIavlTree(t *testing.T) {
// prune version 1
err = tree.Prune(1)
require.NoError(t, err)
require.Equal(t, uint64(3), tree.GetLatestVersion())
v, err = tree.GetLatestVersion()
require.NoError(t, err)
require.Equal(t, uint64(3), v)
// async pruning check
checkErr := func() bool {
if _, err := tree.tree.LoadVersion(1); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions store/v2/commitment/mem/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func (t *Tree) Remove(key []byte) error {
return t.MemDB.Delete(key)
}

func (t *Tree) GetLatestVersion() uint64 {
return 0
func (t *Tree) GetLatestVersion() (uint64, error) {
return 0, nil
}

func (t *Tree) Hash() []byte {
Expand Down
67 changes: 64 additions & 3 deletions store/v2/commitment/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ import (
)

const (
commitInfoKeyFmt = "c/%d" // c/<version>
latestVersionKey = "c/latest"
commitInfoKeyFmt = "c/%d" // c/<version>
latestVersionKey = "c/latest"
removedStoreKeyPrefix = "c/removed/" // c/removed/<version>/<store-name>
)

// MetadataStore is a store for metadata related to the commitment store.
type MetadataStore struct {
kv corestore.KVStoreWithBatch
}

// NewMetadataStore creates a new MetadataStore.
func NewMetadataStore(kv corestore.KVStoreWithBatch) *MetadataStore {
return &MetadataStore{
kv: kv,
}
}

// GetLatestVersion returns the latest committed version.
func (m *MetadataStore) GetLatestVersion() (uint64, error) {
value, err := m.kv.Get([]byte(latestVersionKey))
if err != nil {
Expand All @@ -41,6 +45,16 @@ func (m *MetadataStore) GetLatestVersion() (uint64, error) {
return version, nil
}

func (m *MetadataStore) setLatestVersion(version uint64) error {
var buf bytes.Buffer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use a buf pool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not triggered frequently, I will dig into the next pr regarding the usage of buf pool.

buf.Grow(encoding.EncodeUvarintSize(version))
if err := encoding.EncodeUvarint(&buf, version); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really relevant to this PR, but why are we using varint encoding? Version is a 64 bit uint so isn't a static 8 byte little endian encoded int fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik, it is introduced to optimize the data

return err
}
return m.kv.Set([]byte(latestVersionKey), buf.Bytes())
}
cool-develope marked this conversation as resolved.
Show resolved Hide resolved

// GetCommitInfo returns the commit info for the given version.
func (m *MetadataStore) GetCommitInfo(version uint64) (*proof.CommitInfo, error) {
key := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
value, err := m.kv.Get(key)
Expand Down Expand Up @@ -90,12 +104,59 @@ func (m *MetadataStore) flushCommitInfo(version uint64, cInfo *proof.CommitInfo)
return err
}

if err := batch.WriteSync(); err != nil {
if err := batch.Write(); err != nil {
return err
}
return nil
}

func (m *MetadataStore) flushRemovedStoreKeys(version uint64, storeKeys []string) (err error) {
batch := m.kv.NewBatch()
defer func() {
err = batch.Close()
}()

for _, storeKey := range storeKeys {
key := []byte(fmt.Sprintf("%s%s", encoding.BuildPrefixWithVersion(removedStoreKeyPrefix, version), storeKey))
if err := batch.Set(key, []byte{}); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if a StoreInfo (below)

StoreInfo struct {
Name []byte
CommitID CommitID
Structure string
}

was stored here instead of packing all metadata into the key we would gain two key advantages:

  1. ability to preload (mount) deleted trees at construction time (i.e. in factory)
  2. maintain support for multiple SC types

In particular this PR makes (2) impossible since mountTreeFn is singular.

return err
}
}
return batch.Write()
}

func (m *MetadataStore) deleteRemovedStoreKeys(version uint64, removeStore func(storeKey []byte, version uint64) error) (err error) {
batch := m.kv.NewBatch()
defer func() {
if berr := batch.Close(); berr != nil {
err = berr
}
}()

end := encoding.BuildPrefixWithVersion(removedStoreKeyPrefix, version+1)
iter, err := m.kv.Iterator([]byte(removedStoreKeyPrefix), end)
if err != nil {
return err
}
defer func() {
if ierr := iter.Close(); ierr != nil {
err = ierr
}
}()

for ; iter.Valid(); iter.Next() {
storeKey := iter.Key()[len(end):]
if err := removeStore(storeKey, version); err != nil {
return err
}
if err := batch.Delete(iter.Key()); err != nil {
return nil
}
}

return batch.Write()
}
cool-develope marked this conversation as resolved.
Show resolved Hide resolved

func (m *MetadataStore) deleteCommitInfo(version uint64) error {
cInfoKey := []byte(fmt.Sprintf(commitInfoKeyFmt, version))
return m.kv.Delete(cInfoKey)
Expand Down
Loading
Loading