Use evidence period when pruning (#9505)

* Added logic so when pruning, the evidence period is taken into consideration and only deletes unecessary data
This commit is contained in:
samricotta
2022-10-04 17:57:09 +02:00
committed by GitHub
parent a02cc30e41
commit abbeb919df
12 changed files with 152 additions and 86 deletions

View File

@@ -7,9 +7,11 @@ import (
"github.com/cosmos/gogoproto/proto"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/evidence"
tmsync "github.com/tendermint/tendermint/libs/sync"
tmstore "github.com/tendermint/tendermint/proto/tendermint/store"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
@@ -264,20 +266,20 @@ func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit {
return commit
}
// PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned.
func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
// PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned and the evidence retain height - the height at which data needed to prove evidence must not be removed.
func (bs *BlockStore) PruneBlocks(height int64, state sm.State) (uint64, int64, error) {
if height <= 0 {
return 0, fmt.Errorf("height must be greater than 0")
return 0, -1, fmt.Errorf("height must be greater than 0")
}
bs.mtx.RLock()
if height > bs.height {
bs.mtx.RUnlock()
return 0, fmt.Errorf("cannot prune beyond the latest height %v", bs.height)
return 0, -1, fmt.Errorf("cannot prune beyond the latest height %v", bs.height)
}
base := bs.base
bs.mtx.RUnlock()
if height < base {
return 0, fmt.Errorf("cannot prune to height %v, it is lower than base height %v",
return 0, -1, fmt.Errorf("cannot prune to height %v, it is lower than base height %v",
height, base)
}
@@ -300,26 +302,42 @@ func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
return nil
}
evidencePoint := height
for h := base; h < height; h++ {
meta := bs.LoadBlockMeta(h)
if meta == nil { // assume already deleted
continue
}
if err := batch.Delete(calcBlockMetaKey(h)); err != nil {
return 0, err
// This logic is in place to protect data that proves malicious behavior.
// If the height is within the evidence age, we continue to persist the header and commit data.
if evidencePoint == height && !evidence.IsEvidenceExpired(state.LastBlockHeight, state.LastBlockTime, h, meta.Header.Time, state.ConsensusParams.Evidence) {
evidencePoint = h
}
// if height is beyond the evidence point we dont delete the header
if h < evidencePoint {
if err := batch.Delete(calcBlockMetaKey(h)); err != nil {
return 0, -1, err
}
}
if err := batch.Delete(calcBlockHashKey(meta.BlockID.Hash)); err != nil {
return 0, err
return 0, -1, err
}
if err := batch.Delete(calcBlockCommitKey(h)); err != nil {
return 0, err
// if height is beyond the evidence point we dont delete the commit data
if h < evidencePoint {
if err := batch.Delete(calcBlockCommitKey(h)); err != nil {
return 0, -1, err
}
}
if err := batch.Delete(calcSeenCommitKey(h)); err != nil {
return 0, err
return 0, -1, err
}
for p := 0; p < int(meta.BlockID.PartSetHeader.Total); p++ {
if err := batch.Delete(calcBlockPartKey(h, p)); err != nil {
return 0, err
return 0, -1, err
}
}
pruned++
@@ -328,7 +346,7 @@ func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
if pruned%1000 == 0 && pruned > 0 {
err := flush(batch, h)
if err != nil {
return 0, err
return 0, -1, err
}
batch = bs.db.NewBatch()
defer batch.Close()
@@ -337,9 +355,9 @@ func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) {
err := flush(batch, height)
if err != nil {
return 0, err
return 0, -1, err
}
return pruned, nil
return pruned, evidencePoint, nil
}
// SaveBlock persists the given block, blockParts, and seenCommit to the underlying db.

View File

@@ -381,7 +381,7 @@ func TestLoadBaseMeta(t *testing.T) {
bs.SaveBlock(block, partSet, seenCommit)
}
_, err = bs.PruneBlocks(4)
_, _, err = bs.PruneBlocks(4, state)
require.NoError(t, err)
baseBlock := bs.LoadBaseMeta()
@@ -440,10 +440,10 @@ func TestPruneBlocks(t *testing.T) {
assert.EqualValues(t, 0, bs.Size())
// pruning an empty store should error, even when pruning to 0
_, err = bs.PruneBlocks(1)
_, _, err = bs.PruneBlocks(1, state)
require.Error(t, err)
_, err = bs.PruneBlocks(0)
_, _, err = bs.PruneBlocks(0, state)
require.Error(t, err)
// make more than 1000 blocks, to test batch deletions
@@ -459,27 +459,30 @@ func TestPruneBlocks(t *testing.T) {
assert.EqualValues(t, 1500, bs.Height())
assert.EqualValues(t, 1500, bs.Size())
prunedBlock := bs.LoadBlock(1199)
state.LastBlockTime = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)
state.LastBlockHeight = 1500
state.ConsensusParams.Evidence.MaxAgeNumBlocks = 400
state.ConsensusParams.Evidence.MaxAgeDuration = 1 * time.Second
// Check that basic pruning works
pruned, err := bs.PruneBlocks(1200)
pruned, evidenceRetainHeight, err := bs.PruneBlocks(1200, state)
require.NoError(t, err)
assert.EqualValues(t, 1199, pruned)
assert.EqualValues(t, 1200, bs.Base())
assert.EqualValues(t, 1500, bs.Height())
assert.EqualValues(t, 301, bs.Size())
assert.EqualValues(t, tmstore.BlockStoreState{
Base: 1200,
Height: 1500,
}, LoadBlockStoreState(db))
assert.EqualValues(t, 1100, evidenceRetainHeight)
require.NotNil(t, bs.LoadBlock(1200))
require.Nil(t, bs.LoadBlock(1199))
require.Nil(t, bs.LoadBlockByHash(prunedBlock.Hash()))
require.Nil(t, bs.LoadBlockCommit(1199))
require.Nil(t, bs.LoadBlockMeta(1199))
require.Nil(t, bs.LoadBlockMetaByHash(prunedBlock.Hash()))
require.Nil(t, bs.LoadBlockPart(1199, 1))
// The header and commit for heights 1100 onwards
// need to remain to verify evidence
require.NotNil(t, bs.LoadBlockMeta(1100))
require.Nil(t, bs.LoadBlockMeta(1099))
require.NotNil(t, bs.LoadBlockCommit(1100))
require.Nil(t, bs.LoadBlockCommit(1099))
for i := int64(1); i < 1200; i++ {
require.Nil(t, bs.LoadBlock(i))
@@ -489,26 +492,33 @@ func TestPruneBlocks(t *testing.T) {
}
// Pruning below the current base should error
_, err = bs.PruneBlocks(1199)
_, _, err = bs.PruneBlocks(1199, state)
require.Error(t, err)
// Pruning to the current base should work
pruned, err = bs.PruneBlocks(1200)
pruned, _, err = bs.PruneBlocks(1200, state)
require.NoError(t, err)
assert.EqualValues(t, 0, pruned)
// Pruning again should work
pruned, err = bs.PruneBlocks(1300)
pruned, _, err = bs.PruneBlocks(1300, state)
require.NoError(t, err)
assert.EqualValues(t, 100, pruned)
assert.EqualValues(t, 1300, bs.Base())
// we should still have the header and the commit
// as they're needed for evidence
require.NotNil(t, bs.LoadBlockMeta(1100))
require.Nil(t, bs.LoadBlockMeta(1099))
require.NotNil(t, bs.LoadBlockCommit(1100))
require.Nil(t, bs.LoadBlockCommit(1099))
// Pruning beyond the current height should error
_, err = bs.PruneBlocks(1501)
_, _, err = bs.PruneBlocks(1501, state)
require.Error(t, err)
// Pruning to the current height should work
pruned, err = bs.PruneBlocks(1500)
pruned, _, err = bs.PruneBlocks(1500, state)
require.NoError(t, err)
assert.EqualValues(t, 200, pruned)
assert.Nil(t, bs.LoadBlock(1499))