feat(store/v2): Fallback to SC on queries (#19090)

This commit is contained in:
Aleksandr Bezobchuk 2024-01-19 10:11:15 -05:00 committed by GitHub
parent 2587e756c1
commit 8d0696326b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 107 additions and 12 deletions

View File

@ -67,12 +67,21 @@ func (t *IavlTree) Commit() ([]byte, uint64, error) {
// GetProof returns a proof for the given key and version.
func (t *IavlTree) GetProof(version uint64, key []byte) (*ics23.CommitmentProof, error) {
imutableTree, err := t.tree.GetImmutable(int64(version))
immutableTree, err := t.tree.GetImmutable(int64(version))
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get immutable tree at version %d: %w", version, err)
}
return imutableTree.GetProof(key)
return immutableTree.GetProof(key)
}
func (t *IavlTree) Get(version uint64, key []byte) ([]byte, error) {
immutableTree, err := t.tree.GetImmutable(int64(version))
if err != nil {
return nil, fmt.Errorf("failed to get immutable tree at version %d: %w", version, err)
}
return immutableTree.Get(key)
}
// GetLatestVersion returns the latest version of the tree.

View File

@ -57,6 +57,15 @@ func TestIavlTree(t *testing.T) {
require.Equal(t, workingHash, commitHash)
require.Equal(t, uint64(1), tree.GetLatestVersion())
// ensure we can get expected values
bz, err := tree.Get(1, []byte("key1"))
require.NoError(t, err)
require.Equal(t, []byte("value1"), bz)
bz, err = tree.Get(2, []byte("key1"))
require.Error(t, err)
require.Nil(t, bz)
// write a batch of version 2
require.NoError(t, tree.Set([]byte("key4"), []byte("value4")))
require.NoError(t, tree.Set([]byte("key5"), []byte("value5")))

View File

@ -255,6 +255,20 @@ func (c *CommitStore) GetProof(storeKey string, version uint64, key []byte) ([]s
return []store.CommitmentOp{commitOp, *storeCommitmentOp}, nil
}
func (c *CommitStore) Get(storeKey string, version uint64, key []byte) ([]byte, error) {
tree, ok := c.multiTrees[storeKey]
if !ok {
return nil, fmt.Errorf("store %s not found", storeKey)
}
bz, err := tree.Get(version, key)
if err != nil {
return nil, fmt.Errorf("failed to get key %s from store %s: %w", key, storeKey, err)
}
return bz, nil
}
func (c *CommitStore) Prune(version uint64) (ferr error) {
// prune the metadata
batch := c.db.NewBatch()

View File

@ -17,14 +17,24 @@ type Tree interface {
Set(key, value []byte) error
Remove(key []byte) error
GetLatestVersion() uint64
// Hash returns the hash of the latest saved version of the tree.
Hash() []byte
// WorkingHash returns the working hash of the tree.
WorkingHash() []byte
LoadVersion(version uint64) error
Commit() ([]byte, uint64, error)
SetInitialVersion(version uint64) error
GetProof(version uint64, key []byte) (*ics23.CommitmentProof, error)
// Get attempts to retrieve a value from the tree for a given version.
//
// NOTE: This method only exists to support migration from IAVL v0/v1 to v2.
// Once migration is complete, this method should be removed and/or not used.
Get(version uint64, key []byte) ([]byte, error)
Prune(version uint64) error
Export(version uint64) (Exporter, error)
Import(version uint64) (Importer, error)

View File

@ -69,18 +69,31 @@ type VersionedDatabase interface {
type Committer interface {
// WriteBatch writes a batch of key-value pairs to the tree.
WriteBatch(cs *Changeset) error
// WorkingCommitInfo returns the CommitInfo for the working tree.
WorkingCommitInfo(version uint64) *CommitInfo
// GetLatestVersion returns the latest version.
GetLatestVersion() (uint64, error)
// LoadVersion loads the tree at the given version.
LoadVersion(targetVersion uint64) error
// Commit commits the working tree to the database.
Commit(version uint64) (*CommitInfo, error)
// GetProof returns the proof of existence or non-existence for the given key.
GetProof(storeKey string, version uint64, key []byte) ([]CommitmentOp, error)
// Get returns the value for the given key at the given version.
//
// NOTE: This method only exists to support migration from IAVL v0/v1 to v2.
// Once migration is complete, this method should be removed and/or not used.
Get(storeKey string, version uint64, key []byte) ([]byte, error)
// SetInitialVersion sets the initial version of the tree.
SetInitialVersion(version uint64) error
// GetCommitInfo returns the CommitInfo for the given version.
GetCommitInfo(version uint64) (*CommitInfo, error)

View File

@ -105,13 +105,13 @@ func (s *Store) StateLatest() (uint64, store.ReadOnlyRootStore, error) {
}
func (s *Store) StateAt(v uint64) (store.ReadOnlyRootStore, error) {
// TODO(bez): Ensure the version <v> exists. We can utilize the GetCommitInfo()
// SC method once available.
// TODO(bez): We may want to avoid relying on the SC metadata here. Instead,
// we should add a VersionExists() method to the VersionedDatabase interface.
//
// Ref: https://github.com/cosmos/cosmos-sdk/pull/18736
// if err := s.stateCommitment.GetCommitInfo(v); err != nil {
// return nil, fmt.Errorf("failed to get commit info for version %d: %w", v, err)
// }
// Ref: https://github.com/cosmos/cosmos-sdk/issues/19091
if cInfo, err := s.stateCommitment.GetCommitInfo(v); err != nil || cInfo == nil {
return nil, fmt.Errorf("failed to get commit info for version %d: %w", v, err)
}
return NewReadOnlyAdapter(v, s), nil
}
@ -174,8 +174,23 @@ func (s *Store) Query(storeKey string, version uint64, key []byte, prove bool) (
}
val, err := s.stateStore.Get(storeKey, version, key)
if err != nil {
return store.QueryResult{}, err
if err != nil || val == nil {
// fallback to querying SC backend if not found in SS backend
//
// Note, this should only used during migration, i.e. while SS and IAVL v2
// are being asynchronously synced.
if val == nil {
bz, scErr := s.stateCommitment.Get(storeKey, version, key)
if scErr != nil {
return store.QueryResult{}, fmt.Errorf("failed to query SC store: %w", scErr)
}
val = bz
}
if err != nil {
return store.QueryResult{}, fmt.Errorf("failed to query SS store: %w", err)
}
}
result := store.QueryResult{
@ -187,7 +202,7 @@ func (s *Store) Query(storeKey string, version uint64, key []byte, prove bool) (
if prove {
result.ProofOps, err = s.stateCommitment.GetProof(storeKey, version, key)
if err != nil {
return store.QueryResult{}, err
return store.QueryResult{}, fmt.Errorf("failed to get SC store proof: %w", err)
}
}

View File

@ -104,6 +104,31 @@ func (s *RootStoreTestSuite) TestQuery() {
s.Require().Equal([]byte("foo"), result.ProofOps[0].Key)
}
func (s *RootStoreTestSuite) TestGetFallback() {
sc := s.rootStore.GetStateCommitment()
// create a changeset and commit it to SC ONLY
cs := store.NewChangeset()
cs.Add(testStoreKey, []byte("foo"), []byte("bar"))
err := sc.WriteBatch(cs)
s.Require().NoError(err)
ci := sc.WorkingCommitInfo(1)
_, err = sc.Commit(ci.Version)
s.Require().NoError(err)
// ensure we can query for the key, which should fallback to SC
qResult, err := s.rootStore.Query(testStoreKey, 1, []byte("foo"), false)
s.Require().NoError(err)
s.Require().Equal([]byte("bar"), qResult.Value)
// non-existent key
qResult, err = s.rootStore.Query(testStoreKey, 1, []byte("non_existent_key"), false)
s.Require().NoError(err)
s.Require().Nil(qResult.Value)
}
func (s *RootStoreTestSuite) TestQueryProof() {
cs := store.NewChangeset()
// testStoreKey