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

Tendermint Block Pruning #7265

Merged
merged 29 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
996ec85
Add MinRetainBlocks config opt
alexanderbez Sep 8, 2020
d181ced
Add CLI flag hook
alexanderbez Sep 8, 2020
4e3bb9e
update config doc
alexanderbez Sep 8, 2020
b5f6d5b
update baseapp opts
alexanderbez Sep 8, 2020
7436c83
update simapp
alexanderbez Sep 8, 2020
a918473
Implement GetBlockRentionHeight
alexanderbez Sep 9, 2020
ecec1da
Modify GetBlockRentionHeight
alexanderbez Sep 9, 2020
277e7cc
Modify GetBlockRentionHeight
alexanderbez Sep 9, 2020
5a2ce12
update tests
alexanderbez Sep 9, 2020
a019645
Update ABCI Commit
alexanderbez Sep 9, 2020
d0fe264
use int64
alexanderbez Sep 9, 2020
a2d008e
cl++
alexanderbez Sep 9, 2020
cddfc90
add zero check
alexanderbez Sep 9, 2020
693825b
Merge branch 'master' into bez/6164-tm-block-pruning
alexanderbez Sep 10, 2020
0d48839
fix build
alexanderbez Sep 10, 2020
53f8478
update godocs
alexanderbez Sep 10, 2020
a2a9ab6
lint++
alexanderbez Sep 10, 2020
b43bb87
Merge branch 'master' into bez/6164-tm-block-pruning
fedekunze Sep 11, 2020
451fcb3
Update baseapp/abci.go
alexanderbez Sep 11, 2020
9e766c1
Update baseapp/baseapp.go
alexanderbez Sep 11, 2020
f3f9170
Update server/config/config.go
alexanderbez Sep 11, 2020
ce5726a
Merge branch 'master' into bez/6164-tm-block-pruning
alexanderbez Sep 11, 2020
7a94b11
fix build & refactor min logic
alexanderbez Sep 11, 2020
2c3d62a
godoc++
alexanderbez Sep 11, 2020
94c2a03
adjustments
alexanderbez Sep 11, 2020
b3821ef
godoc++
alexanderbez Sep 11, 2020
21c5429
godoc++
alexanderbez Sep 11, 2020
9016543
Merge branch 'master' into bez/6164-tm-block-pruning
alexanderbez Sep 11, 2020
9fa78d6
Merge branch 'master' into bez/6164-tm-block-pruning
alexanderbez Sep 14, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa

### Features

* [\#7265](https://github.com/cosmos/cosmos-sdk/pull/7265) Support Tendermint block pruning through a new `min-retain-blocks` configuration that can be set in either `app.toml` or via the CLI. This parameter is used in conjunction with other criteria to determine the height at which Tendermint should prune blocks.
* (vesting) [\#7209](https://github.com/cosmos/cosmos-sdk/pull/7209) Create new `MsgCreateVestingAccount` message type along with CLI handler that allows for the creation of delayed and continuous vesting types.
* (events) [\#7121](https://github.com/cosmos/cosmos-sdk/pull/7121) The application now drives what events are indexed by Tendermint via the `index-events` configuration in `app.toml`, which is a list of events taking the form `{eventType}.{attributeKey}`.
* [\#6089](https://github.com/cosmos/cosmos-sdk/pull/6089) Transactions can now have a `TimeoutHeight` set which allows the transaction to be rejected if it's committed at a height greater than the timeout.
Expand Down
89 changes: 88 additions & 1 deletion baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
defer telemetry.MeasureSince(time.Now(), "abci", "commit")

header := app.deliverState.ctx.BlockHeader()
retainHeight := app.GetBlockRentionHeight(header.Height)

// Write the DeliverTx state which is cache-wrapped and commit the MultiStore.
// The write to the DeliverTx state writes all state transitions to the root
Expand Down Expand Up @@ -317,7 +318,8 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
}

return abci.ResponseCommit{
Data: commitID.Hash,
Data: commitID.Hash,
RetainHeight: retainHeight,
}
}

Expand Down Expand Up @@ -561,6 +563,91 @@ func (app *BaseApp) createQueryContext(height int64, prove bool) (sdk.Context, e
return ctx, nil
}

// GetBlockRentionHeight returns the height for which all blocks below this height
// are pruned from Tendermint. Given a commitment height and a non-zero local
// minRetainBlocks configuration, the retentionHeight is the smallest height that
// satisfies:
//
// - Unbonding (safety threshold) time: The block interval in which validators
// can be economically punished for misbehavior. Blocks in this interval must be
// auditable e.g. by the light client.
//
// - Logical store snapshot interval: The block interval at which the underlying
// logical store database is persisted to disk, e.g. every 10000 heights. Blocks
// since the last IAVL snapshot must be available for replay on application restart.
//
// - State sync snapshots: Blocks since the oldest available snapshot must be
// available for state sync nodes to catch up (oldest because a node may be
// restoring an old snapshot while a new snapshot was taken).
//
// - Local (minRetainBlocks) config: Archive nodes may want to retain more or
// all blocks, e.g. via a local config option min-retain-blocks. There may also
// be a need to vary retention for other nodes, e.g. sentry nodes which do not
// need historical blocks.
func (app *BaseApp) GetBlockRentionHeight(commitHeight int64) int64 {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// pruning is disabled if minRetainBlocks is zero
if app.minRetainBlocks == 0 {
return 0
}

min := func(x, y int64) int64 {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
if x < y {
return x
}
return y
}

// Define retentionHeight as the minimum value that satisfies all non-zero
// constraints. All blocks below (commitHeight-retentionHeight) are pruned
// from Tendermint.
var retentionHeight int64

// Define the number of blocks needed to protect against misbehaving validators
// which allows light clients to operate safely. Note, we piggy back of the
// evidence parameters instead of computing an estimated nubmer of blocks based
// on the unbonding period and block commitment time as the two should be
// equivalent.
cp := app.GetConsensusParams(app.deliverState.ctx)
if cp != nil && cp.Evidence != nil && cp.Evidence.MaxAgeNumBlocks > 0 {
retentionHeight = commitHeight - cp.Evidence.MaxAgeNumBlocks
}

// Define the state pruning interval, i.e. the block interval at which the
// underlying logical database is persisted to disk.
statePruningInterval := int64(app.cms.GetPruning().Interval)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should use KeepEvery, not Interval. Consider the case of KeepEvery: 1000 and Interval: 10. At height 10890, this will allow block pruning up to 10880, even though the last state height persisted to disk is 10000 - if the node restarts, it will try to replay from 10000, but can't since the blocks are gone.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right! Can't believe I missed this. Interval is when the heights are actually pruned from disk -- nomenclature always confuses me...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

However, in this case H=10890, and X=H-KeepEvery=9890 which isn't totally correct either. I think we need to do X=H-(H%KeepEvery)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to do X=H-(H%KeepEvery)?

Yes, that's more accurate. H-KeepEvery would work, but has an excessive safety margin. If you go for minNonZero then take care when H<KeepEvery, since this expression results in 0 and thus would be ignored.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

take care when H<KeepEvery, since this expression results in 0 and thus would be ignored.

Correct, in this case, we must be sure to return 0 as to not prune anything because that would be bad!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. @erikgrinaker could you take another look?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good!

It occurred to me that another way to handle this would be to simply return a configuration error if minRetainBlocks was set lower than all these other parameters - instead of magically extending it. Not really sure which UX is better.

if statePruningInterval > 0 {
v := commitHeight - statePruningInterval
if retentionHeight == 0 {
retentionHeight = v
} else {
retentionHeight = min(retentionHeight, v)
}
}

if app.snapshotInterval > 0 && app.snapshotKeepRecent > 0 {
v := commitHeight - int64((app.snapshotInterval * uint64(app.snapshotKeepRecent)))
if retentionHeight == 0 {
retentionHeight = v
} else {
retentionHeight = min(retentionHeight, v)
}
}

v := commitHeight - int64(app.minRetainBlocks)
if retentionHeight == 0 {
retentionHeight = v
} else {
retentionHeight = min(retentionHeight, v)
}

if retentionHeight <= 0 {
// prune nothing in the case of a non-positive height
return 0
}

return retentionHeight
}

func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
if len(path) >= 2 {
switch path[1] {
Expand Down
107 changes: 107 additions & 0 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package baseapp

import (
"testing"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func TestGetBlockRentionHeight(t *testing.T) {
logger := defaultLogger()
db := dbm.NewMemDB()
name := t.Name()

testCases := map[string]struct {
bapp *BaseApp
maxAgeBlocks int64
commitHeight int64
expected int64
}{
"defaults": {
bapp: NewBaseApp(name, logger, db, nil),
maxAgeBlocks: 0,
commitHeight: 499000,
expected: 0,
},
"pruning unbonding time only": {
bapp: NewBaseApp(name, logger, db, nil, SetMinRetainBlocks(1)),
maxAgeBlocks: 362880,
commitHeight: 499000,
expected: 136120,
},
"pruning iavl snapshot only": {
bapp: NewBaseApp(
name, logger, db, nil,
SetPruning(sdk.PruningOptions{Interval: 10000}),
SetMinRetainBlocks(1),
),
maxAgeBlocks: 0,
commitHeight: 499000,
expected: 489000,
},
"pruning state sync snapshot only": {
bapp: NewBaseApp(
name, logger, db, nil,
SetSnapshotInterval(50000),
SetSnapshotKeepRecent(3),
SetMinRetainBlocks(1),
),
maxAgeBlocks: 0,
commitHeight: 499000,
expected: 349000,
},
"pruning min retention only": {
bapp: NewBaseApp(
name, logger, db, nil,
SetMinRetainBlocks(400000),
),
maxAgeBlocks: 0,
commitHeight: 499000,
expected: 99000,
},
"pruning all conditions": {
bapp: NewBaseApp(
name, logger, db, nil,
SetPruning(sdk.PruningOptions{Interval: 10000}),
SetMinRetainBlocks(400000),
SetSnapshotInterval(50000), SetSnapshotKeepRecent(3),
),
maxAgeBlocks: 362880,
commitHeight: 499000,
expected: 99000,
},
"disable pruning": {
bapp: NewBaseApp(
name, logger, db, nil,
SetPruning(sdk.PruningOptions{Interval: 10000}),
SetMinRetainBlocks(0),
SetSnapshotInterval(50000), SetSnapshotKeepRecent(3),
),
maxAgeBlocks: 362880,
commitHeight: 499000,
expected: 0,
},
}

for name, tc := range testCases {
tc := tc

tc.bapp.SetParamStore(&paramStore{db: dbm.NewMemDB()})
tc.bapp.InitChain(abci.RequestInitChain{
ConsensusParams: &abci.ConsensusParams{
Evidence: &tmprototypes.EvidenceParams{
MaxAgeNumBlocks: tc.maxAgeBlocks,
},
},
})

t.Run(name, func(t *testing.T) {
require.Equal(t, tc.expected, tc.bapp.GetBlockRentionHeight(tc.commitHeight))
})
}
}
14 changes: 14 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ type BaseApp struct { // nolint: maligned
// minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown
haltTime uint64

// minRetainBlocksdefines the minimum block height offset from the current
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// block being committed, such that all blocks past this offset are pruned
// from Tendermint. It is used as part of the process of determining the
// ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
// that no blocks should be pruned.
//
// Note: This configuration can and should be used in conjunction with other
// parameters to determine the correct minimum value of ResponseCommit.RetainHeight.
minRetainBlocks uint64

// application's version string
appVersion string

Expand Down Expand Up @@ -298,6 +308,10 @@ func (app *BaseApp) setHaltTime(haltTime uint64) {
app.haltTime = haltTime
}

func (app *BaseApp) setMinRetainBlocks(minRetainBlocks uint64) {
app.minRetainBlocks = minRetainBlocks
}

func (app *BaseApp) setInterBlockCache(cache sdk.MultiStorePersistentCache) {
app.interBlockCache = cache
}
Expand Down
7 changes: 7 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func SetHaltTime(haltTime uint64) func(*BaseApp) {
return func(bap *BaseApp) { bap.setHaltTime(haltTime) }
}

// SetMinRetainBlocks returns a BaseApp option function that sets the minimum
// block retention height value when determining which heights to prune during
// ABCI Commit.
func SetMinRetainBlocks(minRetainBlocks uint64) func(*BaseApp) {
return func(bapp *BaseApp) { bapp.setMinRetainBlocks(minRetainBlocks) }
}

// SetTrace will turn on or off trace flag
func SetTrace(trace bool) func(*BaseApp) {
return func(app *BaseApp) { app.setTrace(trace) }
Expand Down
16 changes: 16 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ type BaseConfig struct {
// Note: Commitment of state will be attempted on the corresponding block.
HaltTime uint64 `mapstructure:"halt-time"`

// MinRetainBlocks defines the minimum block height offset from the current
// block being committed, such that all blocks past this offset are pruned
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// from Tendermint. It is used as part of the process of determining the
// ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
// that no blocks should be pruned.
//
// This configuration value is only responsible for pruning Tendermint blocks.
// It has no bearing on application state pruning which is determined by the
// "pruning-*" configurations.
//
// Note: This configuration can and should be used in conjunction with other
// parameters to determine the correct minimum value of ResponseCommit.RetainHeight.
MinRetainBlocks uint64 `mapstructure:"min-retain-blocks"`

// InterBlockCache enables inter-block caching.
InterBlockCache bool `mapstructure:"inter-block-cache"`

Expand Down Expand Up @@ -150,6 +164,7 @@ func DefaultConfig() *Config {
PruningKeepRecent: "0",
PruningKeepEvery: "0",
PruningInterval: "0",
MinRetainBlocks: 0,
IndexEvents: make([]string, 0),
},
Telemetry: telemetry.Config{
Expand Down Expand Up @@ -197,6 +212,7 @@ func GetConfig(v *viper.Viper) Config {
HaltHeight: v.GetUint64("halt-height"),
HaltTime: v.GetUint64("halt-time"),
IndexEvents: v.GetStringSlice("index-events"),
MinRetainBlocks: v.GetUint64("min-retain-blocks"),
},
Telemetry: telemetry.Config{
ServiceName: v.GetString("telemetry.service-name"),
Expand Down
14 changes: 14 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ halt-height = {{ .BaseConfig.HaltHeight }}
# Note: Commitment of state will be attempted on the corresponding block.
halt-time = {{ .BaseConfig.HaltTime }}

# MinRetainBlocks defines the minimum block height offset from the current
# block being committed, such that all blocks past this offset are pruned
# from Tendermint. It is used as part of the process of determining the
# ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
# that no blocks should be pruned.
#
# This configuration value is only responsible for pruning Tendermint blocks.
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: This configuration can and should be used in conjunction with other
# parameters to determine the correct minimum value of ResponseCommit.RetainHeight.
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
min-retain-blocks = {{ .BaseConfig.MinRetainBlocks }}

# InterBlockCache enables inter-block caching.
inter-block-cache = {{ .BaseConfig.InterBlockCache }}

Expand Down
4 changes: 4 additions & 0 deletions server/mock/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ func (ms multiStore) SetPruning(opts sdk.PruningOptions) {
panic("not implemented")
}

func (ms multiStore) GetPruning() sdk.PruningOptions {
panic("not implemented")
}

func (ms multiStore) GetCommitKVStore(key sdk.StoreKey) sdk.CommitKVStore {
panic("not implemented")
}
Expand Down
2 changes: 2 additions & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
FlagPruningKeepEvery = "pruning-keep-every"
FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
FlagMinRetainBlocks = "min-retain-blocks"
)

// GRPC-related flags.
Expand Down Expand Up @@ -135,6 +136,7 @@ which accepts a path for the resulting pprof file.
cmd.Flags().Uint64(FlagPruningKeepEvery, 0, "Offset heights to keep on disk after 'keep-every' (ignored if pruning is not 'custom')")
cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')")
cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks")
cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks")

cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagGRPCAddress, config.DefaultGRPCAddress, "the gRPC server address to listen on")
Expand Down
1 change: 1 addition & 0 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty
baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))),
baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))),
baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))),
baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))),
baseapp.SetInterBlockCache(cache),
baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))),
baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))),
Expand Down
6 changes: 6 additions & 0 deletions store/iavl/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ func (st *Store) SetPruning(_ types.PruningOptions) {
panic("cannot set pruning options on an initialized IAVL store")
}

// SetPruning panics as pruning options should be provided at initialization
// since IAVl accepts pruning options directly.
func (st *Store) GetPruning() types.PruningOptions {
panic("cannot get pruning options on an initialized IAVL store")
}

// VersionExists returns whether or not a given version is stored.
func (st *Store) VersionExists(version int64) bool {
return st.tree.VersionExists(version)
Expand Down
7 changes: 6 additions & 1 deletion store/mem/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ func (s Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.Cach
func (s *Store) Commit() (id types.CommitID) { return }

func (s *Store) SetPruning(pruning types.PruningOptions) {}
func (s Store) LastCommitID() (id types.CommitID) { return }

// GetPruning is a no-op as pruning options cannot be directly set on this store.
// They must be set on the root commit multi-store.
func (s *Store) GetPruning() types.PruningOptions { return types.PruningOptions{} }

func (s Store) LastCommitID() (id types.CommitID) { return }
4 changes: 4 additions & 0 deletions store/rootmulti/dbadapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ func (cdsa commitDBStoreAdapter) LastCommitID() types.CommitID {
}

func (cdsa commitDBStoreAdapter) SetPruning(_ types.PruningOptions) {}

// GetPruning is a no-op as pruning options cannot be directly set on this store.
// They must be set on the root commit multi-store.
func (cdsa commitDBStoreAdapter) GetPruning() types.PruningOptions { return types.PruningOptions{} }
Loading