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

Fix Restart application issue #5579

Merged
merged 26 commits into from
Feb 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a540b0e
fix commitID along with adding new SnapshotEvery parameter to cleanup…
AdityaSripal Jan 28, 2020
253ad5c
fix commitID along with adding new SnapshotEvery parameter to cleanup…
AdityaSripal Jan 28, 2020
f1e5ba9
remove file from unrelated debugging worl
AdityaSripal Jan 28, 2020
923f1e5
fix cleanup bug
AdityaSripal Jan 28, 2020
a35591b
add iavl tests
AdityaSripal Jan 28, 2020
0ae90d8
add rootmulti tests
AdityaSripal Jan 29, 2020
d612e56
add baseapp tests along with Version method on CommitStore
AdityaSripal Jan 29, 2020
63bd983
cleanup from review
AdityaSripal Jan 29, 2020
9e4b434
Merge branch 'master' into aditya/pruning-fixes
alexanderbez Jan 29, 2020
fbd1a52
Update store/types/pruning.go
AdityaSripal Jan 29, 2020
42fc8ef
initial fixes
AdityaSripal Jan 31, 2020
aa81b5d
fix query bugs by replacing lastCommitID with lastCommitInfo
AdityaSripal Jan 31, 2020
67d5087
Merge branch 'aditya/pruning-fixes' of https://github.com/cosmos/cosm…
AdityaSripal Jan 31, 2020
c9c0fd1
start addressing review comments
AdityaSripal Jan 31, 2020
f041162
further cleanup
AdityaSripal Jan 31, 2020
d52da2c
enforce keepRecent is either 0 or 1
AdityaSripal Feb 5, 2020
f52fa5e
Merge branch 'master' into aditya/pruning-fixes
alexanderbez Feb 5, 2020
5fab6cd
address comments and remove keepRecent from user-facing pruning options
AdityaSripal Feb 6, 2020
c811093
Merge branch 'aditya/pruning-fixes' of https://github.com/cosmos/cosm…
AdityaSripal Feb 6, 2020
4fa7fe7
address last comments on review
AdityaSripal Feb 6, 2020
e8676ab
Linting and cleanup
alexanderbez Feb 6, 2020
d8186a6
More linting
alexanderbez Feb 6, 2020
b210d0e
More linting
alexanderbez Feb 6, 2020
5b00d1a
Edit changelog
alexanderbez Feb 6, 2020
5102a90
Merge branch 'master' into aditya/pruning-fixes
alexanderbez Feb 6, 2020
6e67a1b
Merge branch 'master' into aditya/pruning-fixes
alexanderbez Feb 6, 2020
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,23 @@ balances or a single balance by denom when the `denom` query parameter is presen

### API Breaking Changes

* (types) [\#5579](https://github.com/cosmos/cosmos-sdk/pull/5579) The `keepRecent` field has been removed from the `PruningOptions` type.
The `PruningOptions` type now only includes fields `KeepEvery` and `SnapshotEvery`, where `KeepEvery`
determines which committed heights are flushed to disk and `SnapshotEvery` determines which of these
heights are kept after pruning. The `IsValid` method should be called whenever using these options. Methods
`SnapshotVersion` and `FlushVersion` accept a version arugment and determine if the version should be
flushed to disk or kept as a snapshot. Note, `KeepRecent` is automatically inferred from the options
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for automatically inferring, and less confusion in configs

and provided directly the IAVL store.
* (modules) [\#5555](https://github.com/cosmos/cosmos-sdk/pull/5555) Move x/auth/client/utils/ types and functions to x/auth/client/.
* (modules) [\#5572](https://github.com/cosmos/cosmos-sdk/pull/5572) Move account balance logic and APIs from `x/auth` to `x/bank`.

### Bug Fixes

* (types) [\#5579](https://github.com/cosmos/cosmos-sdk/pull/5579) The IAVL `Store#Commit` method has been refactored to
delete a flushed version if it is not a snapshot version. The root multi-store now keeps track of `commitInfo` instead
of `types.CommitID`. During `Commit` of the root multi-store, `lastCommitInfo` is updated from the saved state
and is only flushed to disk if it is a snapshot version. During `Query` of the root multi-store, if the request height
is the latest height, we'll use the store's `lastCommitInfo`. Otherwise, we fetch `commitInfo` from disk.
* (x/bank) [\#5531](https://github.com/cosmos/cosmos-sdk/issues/5531) Added missing amount event to MsgMultiSend, emitted for each output.
* (client) [\#5618](https://github.com/cosmos/cosmos-sdk/pull/5618) Fix crash on the client when the verifier is not set.

Expand Down
86 changes: 84 additions & 2 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestMountStores(t *testing.T) {
// Test that LoadLatestVersion actually does.
func TestLoadVersion(t *testing.T) {
logger := defaultLogger()
pruningOpt := SetPruning(store.PruneSyncable)
pruningOpt := SetPruning(store.PruneNothing)
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db, nil, pruningOpt)
Expand Down Expand Up @@ -293,7 +293,7 @@ func TestAppVersionSetterGetter(t *testing.T) {

func TestLoadVersionInvalid(t *testing.T) {
logger := log.NewNopLogger()
pruningOpt := SetPruning(store.PruneSyncable)
pruningOpt := SetPruning(store.PruneNothing)
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db, nil, pruningOpt)
Expand Down Expand Up @@ -326,6 +326,88 @@ func TestLoadVersionInvalid(t *testing.T) {
require.Error(t, err)
}

func TestLoadVersionPruning(t *testing.T) {
logger := log.NewNopLogger()
pruningOptions := store.PruningOptions{
KeepEvery: 2,
SnapshotEvery: 6,
}
pruningOpt := SetPruning(pruningOptions)
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db, nil, pruningOpt)

// make a cap key and mount the store
capKey := sdk.NewKVStoreKey(MainStoreKey)
app.MountStores(capKey)
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
require.Nil(t, err)

emptyCommitID := sdk.CommitID{}

// fresh store has zero/empty last commit
lastHeight := app.LastBlockHeight()
lastID := app.LastCommitID()
require.Equal(t, int64(0), lastHeight)
require.Equal(t, emptyCommitID, lastID)

// execute a block
header := abci.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()

// execute a block, collect commit ID
header = abci.Header{Height: 2}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID2 := sdk.CommitID{Version: 2, Hash: res.Data}

// execute a block
header = abci.Header{Height: 3}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID3 := sdk.CommitID{Version: 3, Hash: res.Data}

// reload with LoadLatestVersion, check it loads last flushed version
app = NewBaseApp(name, logger, db, nil, pruningOpt)
app.MountStores(capKey)
err = app.LoadLatestVersion(capKey)
require.Nil(t, err)
testLoadVersionHelper(t, app, int64(2), commitID2)

// re-execute block 3 and check it is same CommitID
header = abci.Header{Height: 3}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
recommitID3 := sdk.CommitID{Version: 3, Hash: res.Data}
require.Equal(t, commitID3, recommitID3, "Commits of identical blocks not equal after reload")

// execute a block, collect commit ID
header = abci.Header{Height: 4}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID4 := sdk.CommitID{Version: 4, Hash: res.Data}

// execute a block
header = abci.Header{Height: 5}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()

// reload with LoadLatestVersion, check it loads last flushed version
app = NewBaseApp(name, logger, db, nil, pruningOpt)
app.MountStores(capKey)
err = app.LoadLatestVersion(capKey)
require.Nil(t, err)
testLoadVersionHelper(t, app, int64(4), commitID4)

// reload with LoadVersion of previous flushed version
// and check it fails since previous flush should be pruned
app = NewBaseApp(name, logger, db, nil, pruningOpt)
app.MountStores(capKey)
err = app.LoadVersion(2, capKey)
require.NotNil(t, err)
}

func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, expectedID sdk.CommitID) {
lastHeight := app.LastBlockHeight()
lastID := app.LastCommitID()
Expand Down
2 changes: 1 addition & 1 deletion server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ default, the application will run with Tendermint in process.

Pruning options can be provided via the '--pruning' flag. The options are as follows:

syncable: only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th)
syncable: only those states not needed for state syncing will be deleted (flushes every 100th to disk and keeps every 10000th)
nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
everything: all saved states will be deleted, storing only the current state

Expand Down
6 changes: 3 additions & 3 deletions store/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestGetOrSetStoreCache(t *testing.T) {
sKey := types.NewKVStoreKey("test")
tree, err := iavl.NewMutableTree(db, 100)
require.NoError(t, err)
store := iavlstore.UnsafeNewStore(tree)
store := iavlstore.UnsafeNewStore(tree, types.PruneNothing)
store2 := mngr.GetStoreCache(sKey, store)

require.NotNil(t, store2)
Expand All @@ -34,7 +34,7 @@ func TestUnwrap(t *testing.T) {
sKey := types.NewKVStoreKey("test")
tree, err := iavl.NewMutableTree(db, 100)
require.NoError(t, err)
store := iavlstore.UnsafeNewStore(tree)
store := iavlstore.UnsafeNewStore(tree, types.PruneNothing)
_ = mngr.GetStoreCache(sKey, store)

require.Equal(t, store, mngr.Unwrap(sKey))
Expand All @@ -48,7 +48,7 @@ func TestStoreCache(t *testing.T) {
sKey := types.NewKVStoreKey("test")
tree, err := iavl.NewMutableTree(db, 100)
require.NoError(t, err)
store := iavlstore.UnsafeNewStore(tree)
store := iavlstore.UnsafeNewStore(tree, types.PruneNothing)
kvStore := mngr.GetStoreCache(sKey, store)

for i := uint(0); i < cache.DefaultCommitKVStoreCacheSize*2; i++ {
Expand Down
68 changes: 58 additions & 10 deletions store/iavl/store.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package iavl

import (
"fmt"
"io"
"sync"

"github.com/pkg/errors"
"github.com/tendermint/iavl"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/merkle"
Expand All @@ -29,18 +31,38 @@ var (

// Store Implements types.KVStore and CommitKVStore.
type Store struct {
tree Tree
tree Tree
pruning types.PruningOptions
}

// LoadStore returns an IAVL Store as a CommitKVStore. Internally it will load the
// LoadStore returns an IAVL Store as a CommitKVStore. Internally, it will load the
// store's version (id) from the provided DB. An error is returned if the version
// fails to load.
func LoadStore(db dbm.DB, id types.CommitID, pruning types.PruningOptions, lazyLoading bool) (types.CommitKVStore, error) {
if !pruning.IsValid() {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("pruning options are invalid: %v", pruning)
}

var keepRecent int64

// Determine the value of keepRecent based on the following:
//
// If KeepEvery = 1, keepRecent should be 0 since there is no need to keep
// latest version in a in-memory cache.
//
// If KeepEvery > 1, keepRecent should be 1 so that state changes in between
Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, that's what 1 does?
I would have though it would have to equal KeepEvery or something.
But I guess it doesn't if we only want "query last block"

// flushed states can be saved in the in-memory latest tree.
if pruning.KeepEvery == 1 {
keepRecent = 0
} else {
keepRecent = 1
}

tree, err := iavl.NewMutableTreeWithOpts(
db,
dbm.NewMemDB(),
defaultIAVLCacheSize,
iavl.PruningOptions(pruning.KeepEvery(), pruning.KeepRecent()),
iavl.PruningOptions(pruning.KeepEvery, keepRecent),
)
if err != nil {
return nil, err
Expand All @@ -56,15 +78,23 @@ func LoadStore(db dbm.DB, id types.CommitID, pruning types.PruningOptions, lazyL
return nil, err
}

return &Store{tree: tree}, nil
return &Store{
tree: tree,
pruning: pruning,
}, nil
}

// UnsafeNewStore returns a reference to a new IAVL Store with a given mutable
// IAVL tree reference.
// IAVL tree reference. It should only be used for testing purposes.
//
// CONTRACT: The IAVL tree should be fully loaded.
func UnsafeNewStore(tree *iavl.MutableTree) *Store {
return &Store{tree: tree}
// CONTRACT: PruningOptions passed in as argument must be the same as pruning options
// passed into iavl.MutableTree
func UnsafeNewStore(tree *iavl.MutableTree, po types.PruningOptions) *Store {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
return &Store{
tree: tree,
pruning: po,
}
}

// GetImmutable returns a reference to a new store backed by an immutable IAVL
Expand All @@ -82,18 +112,36 @@ func (st *Store) GetImmutable(version int64) (*Store, error) {
return nil, err
}

return &Store{tree: &immutableTree{iTree}}, nil
return &Store{
tree: &immutableTree{iTree},
pruning: st.pruning,
}, nil
}

// Implements Committer.
// Commit commits the current store state and returns a CommitID with the new
// version and hash.
func (st *Store) Commit() types.CommitID {
// Save a new version.
hash, version, err := st.tree.SaveVersion()
if err != nil {
// TODO: Do we want to extend Commit to allow returning errors?
panic(err)
}

// If the version we saved got flushed to disk, check if previous flushed
// version should be deleted.
if st.pruning.FlushVersion(version) {
previous := version - st.pruning.KeepEvery

// Previous flushed version should only be pruned if the previous version is
// not a snapshot version OR if snapshotting is disabled (SnapshotEvery == 0).
if previous != 0 && !st.pruning.SnapshotVersion(previous) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe previous > 0 is "safer".
Though I cannot think of a scenario when this could be negative, still better safe

err := st.tree.DeleteVersion(previous)
if errCause := errors.Cause(err); errCause != nil && errCause != iavl.ErrVersionDoesNotExist {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about

if err != nil && !iavl.ErrVersionDoesNotExist.Is(err)

https://github.com/cosmos/cosmos-sdk/blob/master/types/errors/errors.go#L175

Copy link
Contributor

Choose a reason for hiding this comment

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

++

panic(err)
}
}
}

return types.CommitID{
Version: version,
Hash: hash,
Expand Down
Loading