diff --git a/store/CHANGELOG.md b/store/CHANGELOG.md index bc16c71f12..4bf90b2792 100644 --- a/store/CHANGELOG.md +++ b/store/CHANGELOG.md @@ -27,6 +27,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +- [#15712](https://github.com/cosmos/cosmos-sdk/pull/15712) Add `WorkingHash` function to the store interface to get the current app hash before commit. * [#14645](https://github.com/cosmos/cosmos-sdk/pull/14645) Add limit to the length of key and value. * [#15683](https://github.com/cosmos/cosmos-sdk/pull/15683) `rootmulti.Store.CacheMultiStoreWithVersion` now can handle loading archival states that don't persist any of the module stores the current state has. diff --git a/store/iavl/store.go b/store/iavl/store.go index aae3eeaa41..b016db51a0 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -143,6 +143,16 @@ func (st *Store) Commit() types.CommitID { } } +// WorkingHash returns the hash of the current working tree. +func (st *Store) WorkingHash() []byte { + hash, err := st.tree.WorkingHash() + if err != nil { + panic(fmt.Errorf("failed to retrieve working hash: %w", err)) + } + + return hash +} + // LastCommitID implements Committer. func (st *Store) LastCommitID() types.CommitID { hash, err := st.tree.Hash() diff --git a/store/iavl/tree.go b/store/iavl/tree.go index 77762e1dfb..e0581a856a 100644 --- a/store/iavl/tree.go +++ b/store/iavl/tree.go @@ -27,6 +27,7 @@ type ( DeleteVersions(versions ...int64) error Version() int64 Hash() ([]byte, error) + WorkingHash() ([]byte, error) VersionExists(version int64) bool GetVersioned(key []byte, version int64) ([]byte, error) GetImmutable(version int64) (*iavl.ImmutableTree, error) @@ -101,3 +102,7 @@ func (it *immutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, func (it *immutableTree) LazyLoadVersionForOverwriting(targetVersion int64) (int64, error) { panic("cannot call 'LazyLoadVersionForOverwriting' on an immutable IAVL tree") } + +func (it *immutableTree) WorkingHash() ([]byte, error) { + panic("cannot call 'WorkingHash' on an immutable IAVL tree") +} diff --git a/store/mem/store.go b/store/mem/store.go index ca832d7b77..b819d75363 100644 --- a/store/mem/store.go +++ b/store/mem/store.go @@ -58,3 +58,5 @@ func (s *Store) GetPruning() pruningtypes.PruningOptions { } func (s Store) LastCommitID() (id types.CommitID) { return } + +func (s Store) WorkingHash() (hash []byte) { return } diff --git a/store/rootmulti/dbadapter.go b/store/rootmulti/dbadapter.go index 027359a0a5..4f32ada4e9 100644 --- a/store/rootmulti/dbadapter.go +++ b/store/rootmulti/dbadapter.go @@ -9,6 +9,11 @@ import ( var commithash = []byte("FAKE_HASH") +var ( + _ types.KVStore = (*commitDBStoreAdapter)(nil) + _ types.Committer = (*commitDBStoreAdapter)(nil) +) + //---------------------------------------- // commitDBStoreWrapper should only be used for simulation/debugging, // as it doesn't compute any commit hash, and it cannot load older state. @@ -32,6 +37,10 @@ func (cdsa commitDBStoreAdapter) LastCommitID() types.CommitID { } } +func (cdsa commitDBStoreAdapter) WorkingHash() []byte { + return commithash +} + func (cdsa commitDBStoreAdapter) SetPruning(_ pruningtypes.PruningOptions) {} // GetPruning is a no-op as pruning options cannot be directly set on this store. diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index f20902e4d7..d057fcf5b2 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -497,6 +497,37 @@ func (rs *Store) Commit() types.CommitID { } } +// WorkingHash returns the current hash of the store. +// it will be used to get the current app hash before commit. +func (rs *Store) WorkingHash() []byte { + storeInfos := make([]types.StoreInfo, 0, len(rs.stores)) + storeKeys := keysFromStoreKeyMap(rs.stores) + + for _, key := range storeKeys { + store := rs.stores[key] + + if store.GetStoreType() != types.StoreTypeIAVL { + continue + } + + if !rs.removalMap[key] { + si := types.StoreInfo{ + Name: key.Name(), + CommitId: types.CommitID{ + Hash: store.WorkingHash(), + }, + } + storeInfos = append(storeInfos, si) + } + } + + sort.SliceStable(storeInfos, func(i, j int) bool { + return storeInfos[i].Name < storeInfos[j].Name + }) + + return types.CommitInfo{StoreInfos: storeInfos}.Hash() +} + // CacheWrap implements CacheWrapper/Store/CommitStore. func (rs *Store) CacheWrap() types.CacheWrap { return rs.CacheMultiStore().(types.CacheWrap) diff --git a/store/rootmulti/store_test.go b/store/rootmulti/store_test.go index 5557a33fb3..e55687f363 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -128,12 +128,16 @@ func TestHashStableWithEmptyCommit(t *testing.T) { store1 := ms.GetStoreByName("store1").(types.KVStore) store1.Set(k, v) + workingHash := ms.WorkingHash() cID := ms.Commit() require.Equal(t, int64(1), cID.Version) hash := cID.Hash + require.Equal(t, workingHash, hash) // make an empty commit, it should update version, but not affect hash + workingHash = ms.WorkingHash() cID = ms.Commit() + require.Equal(t, workingHash, cID.Hash) require.Equal(t, int64(2), cID.Version) require.Equal(t, hash, cID.Hash) } @@ -159,7 +163,9 @@ func TestMultistoreCommitLoad(t *testing.T) { // Make a few commits and check them. nCommits := int64(3) for i := int64(0); i < nCommits; i++ { + workingHash := store.WorkingHash() commitID = store.Commit() + require.Equal(t, workingHash, commitID.Hash) expectedCommitID := getExpectedCommitID(store, i+1) checkStore(t, store, expectedCommitID, commitID) } @@ -172,7 +178,9 @@ func TestMultistoreCommitLoad(t *testing.T) { checkStore(t, store, commitID, commitID) // Commit and check version. + workingHash := store.WorkingHash() commitID = store.Commit() + require.Equal(t, workingHash, commitID.Hash) expectedCommitID := getExpectedCommitID(store, nCommits+1) checkStore(t, store, expectedCommitID, commitID) @@ -211,7 +219,9 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) { require.Nil(t, s4) // do one commit + workingHash := store.WorkingHash() commitID := store.Commit() + require.Equal(t, workingHash, commitID.Hash) expectedCommitID := getExpectedCommitID(store, 1) checkStore(t, store, expectedCommitID, commitID) diff --git a/store/transient/store.go b/store/transient/store.go index f0ec6cef7e..6f393279f5 100644 --- a/store/transient/store.go +++ b/store/transient/store.go @@ -39,8 +39,12 @@ func (ts *Store) GetPruning() pruningtypes.PruningOptions { } // Implements CommitStore -func (ts *Store) LastCommitID() (id types.CommitID) { - return +func (ts *Store) LastCommitID() types.CommitID { + return types.CommitID{} +} + +func (ts *Store) WorkingHash() []byte { + return []byte{} } // Implements Store. diff --git a/store/types/store.go b/store/types/store.go index 52db4a0875..e2a83bf563 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -22,6 +22,9 @@ type Committer interface { Commit() CommitID LastCommitID() CommitID + // WorkingHash returns the hash of the KVStore's state before commit. + WorkingHash() []byte + SetPruning(pruningtypes.PruningOptions) GetPruning() pruningtypes.PruningOptions }