add support for block pruning via ABCI Commit response (#4588)

* Added BlockStore.DeleteBlock()

* Added initial block pruner prototype

* wip

* Added BlockStore.PruneBlocks()

* Added consensus setting for block pruning

* Added BlockStore base

* Error on replay if base does not have blocks

* Handle missing blocks when sending VoteSetMaj23Message

* Error message tweak

* Properly update blockstore state

* Error message fix again

* blockchain: ignore peer missing blocks

* Added FIXME

* Added test for block replay with truncated history

* Handle peer base in blockchain reactor

* Improved replay error handling

* Added tests for Store.PruneBlocks()

* Fix non-RPC handling of truncated block history

* Panic on missing block meta in needProofBlock()

* Updated changelog

* Handle truncated block history in RPC layer

* Added info about earliest block in /status RPC

* Reorder height and base in blockchain reactor messages

* Updated changelog

* Fix tests

* Appease linter

* Minor review fixes

* Non-empty BlockStores should always have base > 0

* Update code to assume base > 0 invariant

* Added blockstore tests for pruning to 0

* Make sure we don't prune below the current base

* Added BlockStore.Size()

* config: added retain_blocks recommendations

* Update v1 blockchain reactor to handle blockstore base

* Added state database pruning

* Propagate errors on missing validator sets

* Comment tweaks

* Improved error message

Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com>

* use ABCI field ResponseCommit.retain_height instead of retain-blocks config option

* remove State.RetainHeight, return value instead

* fix minor issues

* rename pruneHeights() to pruneBlocks()

* noop to fix GitHub borkage

Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
This commit is contained in:
Erik Grinaker
2020-04-03 10:38:32 +02:00
committed by GitHub
parent ce50dda66c
commit 4298bbcc4e
42 changed files with 1208 additions and 393 deletions

View File

@@ -125,6 +125,102 @@ type ABCIResponses struct {
BeginBlock *abci.ResponseBeginBlock `json:"begin_block"`
}
// PruneStates deletes states between the given heights (including from, excluding to). It is not
// guaranteed to delete all states, since the last checkpointed state and states being pointed to by
// e.g. `LastHeightChanged` must remain. The state at to must also exist.
//
// The from parameter is necessary since we can't do a key scan in a performant way due to the key
// encoding not preserving ordering: https://github.com/tendermint/tendermint/issues/4567
// This will cause some old states to be left behind when doing incremental partial prunes,
// specifically older checkpoints and LastHeightChanged targets.
func PruneStates(db dbm.DB, from int64, to int64) error {
if from <= 0 || to <= 0 {
return fmt.Errorf("from height %v and to height %v must be greater than 0", from, to)
}
if from >= to {
return fmt.Errorf("from height %v must be lower than to height %v", from, to)
}
valInfo := loadValidatorsInfo(db, to)
if valInfo == nil {
return fmt.Errorf("validators at height %v not found", to)
}
paramsInfo := loadConsensusParamsInfo(db, to)
if paramsInfo == nil {
return fmt.Errorf("consensus params at height %v not found", to)
}
keepVals := make(map[int64]bool)
if valInfo.ValidatorSet == nil {
keepVals[valInfo.LastHeightChanged] = true
keepVals[lastStoredHeightFor(to, valInfo.LastHeightChanged)] = true // keep last checkpoint too
}
keepParams := make(map[int64]bool)
if paramsInfo.ConsensusParams.Equals(&types.ConsensusParams{}) {
keepParams[paramsInfo.LastHeightChanged] = true
}
batch := db.NewBatch()
defer batch.Close()
pruned := uint64(0)
var err error
// We have to delete in reverse order, to avoid deleting previous heights that have validator
// sets and consensus params that we may need to retrieve.
for h := to - 1; h >= from; h-- {
// For heights we keep, we must make sure they have the full validator set or consensus
// params, otherwise they will panic if they're retrieved directly (instead of
// indirectly via a LastHeightChanged pointer).
if keepVals[h] {
v := loadValidatorsInfo(db, h)
if v.ValidatorSet == nil {
v.ValidatorSet, err = LoadValidators(db, h)
if err != nil {
return err
}
v.LastHeightChanged = h
batch.Set(calcValidatorsKey(h), v.Bytes())
}
} else {
batch.Delete(calcValidatorsKey(h))
}
if keepParams[h] {
p := loadConsensusParamsInfo(db, h)
if p.ConsensusParams.Equals(&types.ConsensusParams{}) {
p.ConsensusParams, err = LoadConsensusParams(db, h)
if err != nil {
return err
}
p.LastHeightChanged = h
batch.Set(calcConsensusParamsKey(h), p.Bytes())
}
} else {
batch.Delete(calcConsensusParamsKey(h))
}
batch.Delete(calcABCIResponsesKey(h))
pruned++
// avoid batches growing too large by flushing to database regularly
if pruned%1000 == 0 && pruned > 0 {
err := batch.Write()
if err != nil {
return err
}
batch.Close()
batch = db.NewBatch()
defer batch.Close()
}
}
err = batch.WriteSync()
if err != nil {
return err
}
return nil
}
// NewABCIResponses returns a new ABCIResponses
func NewABCIResponses(block *types.Block) *ABCIResponses {
resDeliverTxs := make([]*abci.ResponseDeliverTx, len(block.Data.Txs))