refactor!: abstractions for snapshot and pruning; snapshot intervals eventually pruned; unit tests (#11496)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Pruning
|
||||
|
||||
## Overview
|
||||
|
||||
Pruning is the mechanism for deleting old application heights from the disk. Depending on the use case,
|
||||
nodes may require different pruning strategies. For example, archive nodes must keep all
|
||||
the states and prune nothing. On the other hand, a regular validator node may want to only keep 100 latest heights for performance reasons.
|
||||
|
||||
## Strategies
|
||||
|
||||
The strategies are configured in `app.toml`, with the format `pruning = "<strategy>"` where the options are:
|
||||
|
||||
- `default`: only the last 362,880 states(approximately 3.5 weeks worth of state) are kept; pruning at 10 block intervals
|
||||
- `nothing`: all historic states will be saved, nothing will be deleted (i.e. archiving node)
|
||||
- `everything`: 2 latest states will be kept; pruning at 10 block intervals.
|
||||
- `custom`: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval'
|
||||
|
||||
If no strategy is given to the BaseApp, `nothing` is selected. However, we perform validation on the CLI layer to require these to be always set in the config file.
|
||||
|
||||
## Custom Pruning
|
||||
|
||||
These are applied if and only if the pruning strategy is custom:
|
||||
- `pruning-keep-recent`: N means to keep all of the last N states
|
||||
- `pruning-interval`: N means to delete old states from disk every Nth block.
|
||||
|
||||
## Relationship to State Sync Snapshots
|
||||
|
||||
Snapshot settings are optional. However, if set, they have an effect on how pruning is done by
|
||||
persisting the heights that are multiples of `state-sync.snapshot-interval` until after the snapshot is complete. See the "Relationship to Pruning" section in `snapshots/README.md` for more details.
|
||||
@@ -0,0 +1,11 @@
|
||||
package pruning
|
||||
|
||||
var (
|
||||
PruneHeightsKey = pruneHeightsKey
|
||||
PruneSnapshotHeightsKey = pruneSnapshotHeightsKey
|
||||
|
||||
Int64SliceToBytes = int64SliceToBytes
|
||||
ListToBytes = listToBytes
|
||||
LoadPruningHeights = loadPruningHeights
|
||||
LoadPruningSnapshotHeights = loadPruningSnapshotHeights
|
||||
)
|
||||
@@ -0,0 +1,282 @@
|
||||
package pruning
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/pruning/types"
|
||||
)
|
||||
|
||||
// Manager is an abstraction to handle the logic needed for
|
||||
// determinging when to prune old heights of the store
|
||||
// based on the strategy described by the pruning options.
|
||||
type Manager struct {
|
||||
db dbm.DB
|
||||
logger log.Logger
|
||||
opts types.PruningOptions
|
||||
snapshotInterval uint64
|
||||
// Although pruneHeights happen in the same goroutine with the normal execution,
|
||||
// we sync access to them to avoid soundness issues in the future if concurrency pattern changes.
|
||||
pruneHeightsMx sync.Mutex
|
||||
pruneHeights []int64
|
||||
// Snapshots are taken in a separate goroutine from the regular execution
|
||||
// and can be delivered asynchrounously via HandleHeightSnapshot.
|
||||
// Therefore, we sync access to pruneSnapshotHeights with this mutex.
|
||||
pruneSnapshotHeightsMx sync.Mutex
|
||||
// These are the heights that are multiples of snapshotInterval and kept for state sync snapshots.
|
||||
// The heights are added to this list to be pruned when a snapshot is complete.
|
||||
pruneSnapshotHeights *list.List
|
||||
}
|
||||
|
||||
// NegativeHeightsError is returned when a negative height is provided to the manager.
|
||||
type NegativeHeightsError struct {
|
||||
Height int64
|
||||
}
|
||||
|
||||
var _ error = &NegativeHeightsError{}
|
||||
|
||||
func (e *NegativeHeightsError) Error() string {
|
||||
return fmt.Sprintf("failed to get pruned heights: %d", e.Height)
|
||||
}
|
||||
|
||||
var (
|
||||
pruneHeightsKey = []byte("s/pruneheights")
|
||||
pruneSnapshotHeightsKey = []byte("s/prunesnapshotheights")
|
||||
)
|
||||
|
||||
// NewManager returns a new Manager with the given db and logger.
|
||||
// The retuned manager uses a pruning strategy of "nothing" which
|
||||
// keeps all heights. Users of the Manager may change the strategy
|
||||
// by calling SetOptions.
|
||||
func NewManager(db dbm.DB, logger log.Logger) *Manager {
|
||||
return &Manager{
|
||||
db: db,
|
||||
logger: logger,
|
||||
opts: types.NewPruningOptions(types.PruningNothing),
|
||||
pruneHeights: []int64{},
|
||||
pruneSnapshotHeights: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetOptions sets the pruning strategy on the manager.
|
||||
func (m *Manager) SetOptions(opts types.PruningOptions) {
|
||||
m.opts = opts
|
||||
}
|
||||
|
||||
// GetOptions fetches the pruning strategy from the manager.
|
||||
func (m *Manager) GetOptions() types.PruningOptions {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
// GetFlushAndResetPruningHeights returns all heights to be pruned during the next call to Prune().
|
||||
// It also flushes and resets the pruning heights.
|
||||
func (m *Manager) GetFlushAndResetPruningHeights() ([]int64, error) {
|
||||
if m.opts.GetPruningStrategy() == types.PruningNothing {
|
||||
return []int64{}, nil
|
||||
}
|
||||
m.pruneHeightsMx.Lock()
|
||||
defer m.pruneHeightsMx.Unlock()
|
||||
|
||||
// flush the updates to disk so that it is not lost if crash happens.
|
||||
if err := m.db.SetSync(pruneHeightsKey, int64SliceToBytes(m.pruneHeights)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return a copy to prevent data races.
|
||||
pruningHeights := make([]int64, len(m.pruneHeights))
|
||||
copy(pruningHeights, m.pruneHeights)
|
||||
m.pruneHeights = m.pruneHeights[:0]
|
||||
|
||||
return pruningHeights, nil
|
||||
}
|
||||
|
||||
// HandleHeight determines if previousHeight height needs to be kept for pruning at the right interval prescribed by
|
||||
// the pruning strategy. Returns previousHeight, if it was kept to be pruned at the next call to Prune(), 0 otherwise.
|
||||
// previousHeight must be greater than 0 for the handling to take effect since valid heights start at 1 and 0 represents
|
||||
// the latest height. The latest height cannot be pruned. As a result, if previousHeight is less than or equal to 0, 0 is returned.
|
||||
func (m *Manager) HandleHeight(previousHeight int64) int64 {
|
||||
if m.opts.GetPruningStrategy() == types.PruningNothing || previousHeight <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
defer func() {
|
||||
m.pruneHeightsMx.Lock()
|
||||
defer m.pruneHeightsMx.Unlock()
|
||||
|
||||
m.pruneSnapshotHeightsMx.Lock()
|
||||
defer m.pruneSnapshotHeightsMx.Unlock()
|
||||
|
||||
// move persisted snapshot heights to pruneHeights which
|
||||
// represent the heights to be pruned at the next pruning interval.
|
||||
var next *list.Element
|
||||
for e := m.pruneSnapshotHeights.Front(); e != nil; e = next {
|
||||
snHeight := e.Value.(int64)
|
||||
if snHeight < previousHeight-int64(m.opts.KeepRecent) {
|
||||
m.pruneHeights = append(m.pruneHeights, snHeight)
|
||||
|
||||
// We must get next before removing to be able to continue iterating.
|
||||
next = e.Next()
|
||||
m.pruneSnapshotHeights.Remove(e)
|
||||
} else {
|
||||
next = e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// flush the updates to disk so that they are not lost if crash happens.
|
||||
if err := m.db.SetSync(pruneHeightsKey, int64SliceToBytes(m.pruneHeights)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if int64(m.opts.KeepRecent) < previousHeight {
|
||||
pruneHeight := previousHeight - int64(m.opts.KeepRecent)
|
||||
// We consider this height to be pruned iff:
|
||||
//
|
||||
// - snapshotInterval is zero as that means that all heights should be pruned.
|
||||
// - snapshotInterval % (height - KeepRecent) != 0 as that means the height is not
|
||||
// a 'snapshot' height.
|
||||
if m.snapshotInterval == 0 || pruneHeight%int64(m.snapshotInterval) != 0 {
|
||||
m.pruneHeightsMx.Lock()
|
||||
defer m.pruneHeightsMx.Unlock()
|
||||
|
||||
m.pruneHeights = append(m.pruneHeights, pruneHeight)
|
||||
return pruneHeight
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HandleHeightSnapshot persists the snapshot height to be pruned at the next appropriate
|
||||
// height defined by the pruning strategy. Flushes the update to disk and panics if the flush fails
|
||||
// The input height must be greater than 0 and pruning strategy any but pruning nothing.
|
||||
// If one of these conditions is not met, this function does nothing.
|
||||
func (m *Manager) HandleHeightSnapshot(height int64) {
|
||||
if m.opts.GetPruningStrategy() == types.PruningNothing || height <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
m.pruneSnapshotHeightsMx.Lock()
|
||||
defer m.pruneSnapshotHeightsMx.Unlock()
|
||||
|
||||
m.logger.Debug("HandleHeightSnapshot", "height", height)
|
||||
m.pruneSnapshotHeights.PushBack(height)
|
||||
|
||||
// flush the updates to disk so that they are not lost if crash happens.
|
||||
if err := m.db.SetSync(pruneSnapshotHeightsKey, listToBytes(m.pruneSnapshotHeights)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSnapshotInterval sets the interval at which the snapshots are taken.
|
||||
func (m *Manager) SetSnapshotInterval(snapshotInterval uint64) {
|
||||
m.snapshotInterval = snapshotInterval
|
||||
}
|
||||
|
||||
// ShouldPruneAtHeight return true if the given height should be pruned, false otherwise
|
||||
func (m *Manager) ShouldPruneAtHeight(height int64) bool {
|
||||
return m.opts.Interval > 0 && m.opts.GetPruningStrategy() != types.PruningNothing && height%int64(m.opts.Interval) == 0
|
||||
}
|
||||
|
||||
// LoadPruningHeights loads the pruning heights from the database as a crash recovery.
|
||||
func (m *Manager) LoadPruningHeights(db dbm.DB) error {
|
||||
if m.opts.GetPruningStrategy() == types.PruningNothing {
|
||||
return nil
|
||||
}
|
||||
loadedPruneHeights, err := loadPruningHeights(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(loadedPruneHeights) > 0 {
|
||||
m.pruneHeightsMx.Lock()
|
||||
defer m.pruneHeightsMx.Unlock()
|
||||
m.pruneHeights = loadedPruneHeights
|
||||
}
|
||||
|
||||
loadedPruneSnapshotHeights, err := loadPruningSnapshotHeights(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loadedPruneSnapshotHeights.Len() > 0 {
|
||||
m.pruneSnapshotHeightsMx.Lock()
|
||||
defer m.pruneSnapshotHeightsMx.Unlock()
|
||||
m.pruneSnapshotHeights = loadedPruneSnapshotHeights
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadPruningHeights(db dbm.DB) ([]int64, error) {
|
||||
bz, err := db.Get(pruneHeightsKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get pruned heights: %w", err)
|
||||
}
|
||||
if len(bz) == 0 {
|
||||
return []int64{}, nil
|
||||
}
|
||||
|
||||
prunedHeights := make([]int64, len(bz)/8)
|
||||
i, offset := 0, 0
|
||||
for offset < len(bz) {
|
||||
h := int64(binary.BigEndian.Uint64(bz[offset : offset+8]))
|
||||
if h < 0 {
|
||||
return []int64{}, &NegativeHeightsError{Height: h}
|
||||
}
|
||||
|
||||
prunedHeights[i] = h
|
||||
i++
|
||||
offset += 8
|
||||
}
|
||||
|
||||
return prunedHeights, nil
|
||||
}
|
||||
|
||||
func loadPruningSnapshotHeights(db dbm.DB) (*list.List, error) {
|
||||
bz, err := db.Get(pruneSnapshotHeightsKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get post-snapshot pruned heights: %w", err)
|
||||
}
|
||||
pruneSnapshotHeights := list.New()
|
||||
if len(bz) == 0 {
|
||||
return pruneSnapshotHeights, nil
|
||||
}
|
||||
|
||||
i, offset := 0, 0
|
||||
for offset < len(bz) {
|
||||
h := int64(binary.BigEndian.Uint64(bz[offset : offset+8]))
|
||||
if h < 0 {
|
||||
return nil, &NegativeHeightsError{Height: h}
|
||||
}
|
||||
pruneSnapshotHeights.PushBack(h)
|
||||
i++
|
||||
offset += 8
|
||||
}
|
||||
|
||||
return pruneSnapshotHeights, nil
|
||||
}
|
||||
|
||||
func int64SliceToBytes(slice []int64) []byte {
|
||||
bz := make([]byte, 0, len(slice)*8)
|
||||
for _, ph := range slice {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(ph))
|
||||
bz = append(bz, buf...)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
func listToBytes(list *list.List) []byte {
|
||||
bz := make([]byte, 0, list.Len()*8)
|
||||
for e := list.Front(); e != nil; e = e.Next() {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(e.Value.(int64)))
|
||||
bz = append(bz, buf...)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
package pruning_test
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
db "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/pruning"
|
||||
"github.com/cosmos/cosmos-sdk/pruning/mock"
|
||||
"github.com/cosmos/cosmos-sdk/pruning/types"
|
||||
)
|
||||
|
||||
const dbErr = "db error"
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
|
||||
|
||||
require.NotNil(t, manager)
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, heights)
|
||||
require.Equal(t, types.PruningNothing, manager.GetOptions().GetPruningStrategy())
|
||||
}
|
||||
|
||||
func TestStrategies(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
strategy types.PruningOptions
|
||||
snapshotInterval uint64
|
||||
strategyToAssert types.PruningStrategy
|
||||
isValid bool
|
||||
}{
|
||||
"prune nothing - no snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningNothing),
|
||||
strategyToAssert: types.PruningNothing,
|
||||
},
|
||||
"prune nothing - snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningNothing),
|
||||
strategyToAssert: types.PruningNothing,
|
||||
snapshotInterval: 100,
|
||||
},
|
||||
"prune default - no snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningDefault),
|
||||
strategyToAssert: types.PruningDefault,
|
||||
},
|
||||
"prune default - snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningDefault),
|
||||
strategyToAssert: types.PruningDefault,
|
||||
snapshotInterval: 100,
|
||||
},
|
||||
"prune everything - no snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningEverything),
|
||||
strategyToAssert: types.PruningEverything,
|
||||
},
|
||||
"prune everything - snapshot": {
|
||||
strategy: types.NewPruningOptions(types.PruningEverything),
|
||||
strategyToAssert: types.PruningEverything,
|
||||
snapshotInterval: 100,
|
||||
},
|
||||
"custom 100-10-15": {
|
||||
strategy: types.NewCustomPruningOptions(100, 15),
|
||||
snapshotInterval: 10,
|
||||
strategyToAssert: types.PruningCustom,
|
||||
},
|
||||
"custom 10-10-15": {
|
||||
strategy: types.NewCustomPruningOptions(10, 15),
|
||||
snapshotInterval: 10,
|
||||
strategyToAssert: types.PruningCustom,
|
||||
},
|
||||
"custom 100-0-15": {
|
||||
strategy: types.NewCustomPruningOptions(100, 15),
|
||||
snapshotInterval: 0,
|
||||
strategyToAssert: types.PruningCustom,
|
||||
},
|
||||
}
|
||||
|
||||
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
|
||||
|
||||
require.NotNil(t, manager)
|
||||
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
curStrategy := tc.strategy
|
||||
manager.SetSnapshotInterval(tc.snapshotInterval)
|
||||
|
||||
pruneStrategy := curStrategy.GetPruningStrategy()
|
||||
require.Equal(t, tc.strategyToAssert, pruneStrategy)
|
||||
|
||||
// Validate strategy parameters
|
||||
switch pruneStrategy {
|
||||
case types.PruningDefault:
|
||||
require.Equal(t, uint64(362880), curStrategy.KeepRecent)
|
||||
require.Equal(t, uint64(10), curStrategy.Interval)
|
||||
case types.PruningNothing:
|
||||
require.Equal(t, uint64(0), curStrategy.KeepRecent)
|
||||
require.Equal(t, uint64(0), curStrategy.Interval)
|
||||
case types.PruningEverything:
|
||||
require.Equal(t, uint64(2), curStrategy.KeepRecent)
|
||||
require.Equal(t, uint64(10), curStrategy.Interval)
|
||||
default:
|
||||
//
|
||||
}
|
||||
|
||||
manager.SetOptions(curStrategy)
|
||||
require.Equal(t, tc.strategy, manager.GetOptions())
|
||||
|
||||
curKeepRecent := curStrategy.KeepRecent
|
||||
curInterval := curStrategy.Interval
|
||||
|
||||
for curHeight := int64(0); curHeight < 110000; curHeight++ {
|
||||
handleHeightActual := manager.HandleHeight(curHeight)
|
||||
shouldPruneAtHeightActual := manager.ShouldPruneAtHeight(curHeight)
|
||||
|
||||
curPruningHeihts, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.Nil(t, err)
|
||||
|
||||
curHeightStr := fmt.Sprintf("height: %d", curHeight)
|
||||
|
||||
switch curStrategy.GetPruningStrategy() {
|
||||
case types.PruningNothing:
|
||||
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
|
||||
require.False(t, shouldPruneAtHeightActual, curHeightStr)
|
||||
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(heights))
|
||||
default:
|
||||
if curHeight > int64(curKeepRecent) && (tc.snapshotInterval != 0 && (curHeight-int64(curKeepRecent))%int64(tc.snapshotInterval) != 0 || tc.snapshotInterval == 0) {
|
||||
expectedHeight := curHeight - int64(curKeepRecent)
|
||||
require.Equal(t, curHeight-int64(curKeepRecent), handleHeightActual, curHeightStr)
|
||||
|
||||
require.Contains(t, curPruningHeihts, expectedHeight, curHeightStr)
|
||||
} else {
|
||||
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
|
||||
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(heights))
|
||||
}
|
||||
require.Equal(t, curHeight%int64(curInterval) == 0, shouldPruneAtHeightActual, curHeightStr)
|
||||
}
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(heights))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHeight_Inputs(t *testing.T) {
|
||||
var keepRecent int64 = int64(types.NewPruningOptions(types.PruningEverything).KeepRecent)
|
||||
|
||||
testcases := map[string]struct {
|
||||
height int64
|
||||
expectedResult int64
|
||||
strategy types.PruningStrategy
|
||||
expectedHeights []int64
|
||||
}{
|
||||
"previousHeight is negative - prune everything - invalid previousHeight": {
|
||||
-1,
|
||||
0,
|
||||
types.PruningEverything,
|
||||
[]int64{},
|
||||
},
|
||||
"previousHeight is zero - prune everything - invalid previousHeight": {
|
||||
0,
|
||||
0,
|
||||
types.PruningEverything,
|
||||
[]int64{},
|
||||
},
|
||||
"previousHeight is positive but within keep recent- prune everything - not kept": {
|
||||
keepRecent,
|
||||
0,
|
||||
types.PruningEverything,
|
||||
[]int64{},
|
||||
},
|
||||
"previousHeight is positive and greater than keep recent - kept": {
|
||||
keepRecent + 1,
|
||||
keepRecent + 1 - keepRecent,
|
||||
types.PruningEverything,
|
||||
[]int64{keepRecent + 1 - keepRecent},
|
||||
},
|
||||
"pruning nothing, previousHeight is positive and greater than keep recent - not kept": {
|
||||
keepRecent + 1,
|
||||
0,
|
||||
types.PruningNothing,
|
||||
[]int64{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
|
||||
require.NotNil(t, manager)
|
||||
manager.SetOptions(types.NewPruningOptions(tc.strategy))
|
||||
|
||||
handleHeightActual := manager.HandleHeight(tc.height)
|
||||
require.Equal(t, tc.expectedResult, handleHeightActual)
|
||||
|
||||
actualHeights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tc.expectedHeights), len(actualHeights))
|
||||
require.Equal(t, tc.expectedHeights, actualHeights)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHeight_FlushLoadFromDisk(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
previousHeight int64
|
||||
keepRecent uint64
|
||||
snapshotInterval uint64
|
||||
movedSnapshotHeights []int64
|
||||
expectedHandleHeightResult int64
|
||||
expectedLoadPruningHeightsResult error
|
||||
expectedLoadedHeights []int64
|
||||
}{
|
||||
"simple flush occurs": {
|
||||
previousHeight: 11,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 0,
|
||||
movedSnapshotHeights: []int64{},
|
||||
expectedHandleHeightResult: 11 - 10,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{11 - 10},
|
||||
},
|
||||
"previous height <= keep recent - no update and no flush": {
|
||||
previousHeight: 9,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 0,
|
||||
movedSnapshotHeights: []int64{},
|
||||
expectedHandleHeightResult: 0,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{},
|
||||
},
|
||||
"previous height alligns with snapshot interval - no update and no flush": {
|
||||
previousHeight: 12,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 2,
|
||||
movedSnapshotHeights: []int64{},
|
||||
expectedHandleHeightResult: 0,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{},
|
||||
},
|
||||
"previous height does not align with snapshot interval - flush": {
|
||||
previousHeight: 12,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 3,
|
||||
movedSnapshotHeights: []int64{},
|
||||
expectedHandleHeightResult: 2,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{2},
|
||||
},
|
||||
"moved snapshot heights - flushed": {
|
||||
previousHeight: 32,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 5,
|
||||
movedSnapshotHeights: []int64{15, 20, 25},
|
||||
expectedHandleHeightResult: 22,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{15, 20, 22},
|
||||
},
|
||||
"previous height alligns with snapshot interval - no update but flush snapshot heights": {
|
||||
previousHeight: 30,
|
||||
keepRecent: 10,
|
||||
snapshotInterval: 5,
|
||||
movedSnapshotHeights: []int64{15, 20, 25},
|
||||
expectedHandleHeightResult: 0,
|
||||
expectedLoadPruningHeightsResult: nil,
|
||||
expectedLoadedHeights: []int64{15},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Setup
|
||||
db := db.NewMemDB()
|
||||
manager := pruning.NewManager(db, log.NewNopLogger())
|
||||
require.NotNil(t, manager)
|
||||
|
||||
manager.SetSnapshotInterval(tc.snapshotInterval)
|
||||
manager.SetOptions(types.NewCustomPruningOptions(uint64(tc.keepRecent), uint64(10)))
|
||||
|
||||
for _, snapshotHeight := range tc.movedSnapshotHeights {
|
||||
manager.HandleHeightSnapshot(snapshotHeight)
|
||||
}
|
||||
|
||||
// Test HandleHeight and flush
|
||||
handleHeightActual := manager.HandleHeight(tc.previousHeight)
|
||||
require.Equal(t, tc.expectedHandleHeightResult, handleHeightActual)
|
||||
|
||||
loadedPruneHeights, err := pruning.LoadPruningHeights(db)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(loadedPruneHeights), len(loadedPruneHeights))
|
||||
|
||||
// Test load back
|
||||
err = manager.LoadPruningHeights(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tc.expectedLoadedHeights), len(heights))
|
||||
require.ElementsMatch(t, tc.expectedLoadedHeights, heights)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHeight_DbErr_Panic(t *testing.T) {
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
// Setup
|
||||
dbMock := mock.NewMockDB(ctrl)
|
||||
|
||||
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
|
||||
|
||||
manager := pruning.NewManager(dbMock, log.NewNopLogger())
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
|
||||
require.NotNil(t, manager)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}()
|
||||
|
||||
manager.HandleHeight(10)
|
||||
}
|
||||
|
||||
func TestHandleHeightSnapshot_FlushLoadFromDisk(t *testing.T) {
|
||||
loadedHeightsMirror := []int64{}
|
||||
|
||||
// Setup
|
||||
db := db.NewMemDB()
|
||||
manager := pruning.NewManager(db, log.NewNopLogger())
|
||||
require.NotNil(t, manager)
|
||||
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
|
||||
|
||||
for snapshotHeight := int64(-1); snapshotHeight < 100; snapshotHeight++ {
|
||||
// Test flush
|
||||
manager.HandleHeightSnapshot(snapshotHeight)
|
||||
|
||||
// Post test
|
||||
if snapshotHeight > 0 {
|
||||
loadedHeightsMirror = append(loadedHeightsMirror, snapshotHeight)
|
||||
}
|
||||
|
||||
loadedSnapshotHeights, err := pruning.LoadPruningSnapshotHeights(db)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(loadedHeightsMirror), loadedSnapshotHeights.Len())
|
||||
|
||||
// Test load back
|
||||
err = manager.LoadPruningHeights(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
loadedSnapshotHeights, err = pruning.LoadPruningSnapshotHeights(db)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(loadedHeightsMirror), loadedSnapshotHeights.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHeightSnapshot_DbErr_Panic(t *testing.T) {
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
// Setup
|
||||
dbMock := mock.NewMockDB(ctrl)
|
||||
|
||||
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
|
||||
|
||||
manager := pruning.NewManager(dbMock, log.NewNopLogger())
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
|
||||
require.NotNil(t, manager)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}()
|
||||
|
||||
manager.HandleHeightSnapshot(10)
|
||||
}
|
||||
|
||||
func TestFlushLoad(t *testing.T) {
|
||||
db := db.NewMemDB()
|
||||
manager := pruning.NewManager(db, log.NewNopLogger())
|
||||
require.NotNil(t, manager)
|
||||
|
||||
curStrategy := types.NewCustomPruningOptions(100, 15)
|
||||
|
||||
snapshotInterval := uint64(10)
|
||||
manager.SetSnapshotInterval(snapshotInterval)
|
||||
|
||||
manager.SetOptions(curStrategy)
|
||||
require.Equal(t, curStrategy, manager.GetOptions())
|
||||
|
||||
keepRecent := curStrategy.KeepRecent
|
||||
|
||||
heightsToPruneMirror := make([]int64, 0)
|
||||
|
||||
for curHeight := int64(0); curHeight < 1000; curHeight++ {
|
||||
handleHeightActual := manager.HandleHeight(curHeight)
|
||||
|
||||
curHeightStr := fmt.Sprintf("height: %d", curHeight)
|
||||
|
||||
if curHeight > int64(keepRecent) && (snapshotInterval != 0 && (curHeight-int64(keepRecent))%int64(snapshotInterval) != 0 || snapshotInterval == 0) {
|
||||
expectedHandleHeight := curHeight - int64(keepRecent)
|
||||
require.Equal(t, expectedHandleHeight, handleHeightActual, curHeightStr)
|
||||
heightsToPruneMirror = append(heightsToPruneMirror, expectedHandleHeight)
|
||||
} else {
|
||||
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
|
||||
}
|
||||
|
||||
if manager.ShouldPruneAtHeight(curHeight) && curHeight > int64(keepRecent) {
|
||||
actualHeights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(heightsToPruneMirror), len(actualHeights))
|
||||
require.Equal(t, heightsToPruneMirror, actualHeights)
|
||||
|
||||
err = manager.LoadPruningHeights(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
actualHeights, err = manager.GetFlushAndResetPruningHeights()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(heightsToPruneMirror), len(actualHeights))
|
||||
require.Equal(t, heightsToPruneMirror, actualHeights)
|
||||
|
||||
heightsToPruneMirror = make([]int64, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPruningHeights(t *testing.T) {
|
||||
var (
|
||||
manager = pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
|
||||
err error
|
||||
)
|
||||
require.NotNil(t, manager)
|
||||
|
||||
// must not be PruningNothing
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningDefault))
|
||||
|
||||
testcases := map[string]struct {
|
||||
flushedPruningHeights []int64
|
||||
getFlushedPruningSnapshotHeights func() *list.List
|
||||
expectedResult error
|
||||
}{
|
||||
"negative pruningHeight - error": {
|
||||
flushedPruningHeights: []int64{10, 0, -1},
|
||||
expectedResult: &pruning.NegativeHeightsError{Height: -1},
|
||||
},
|
||||
"negative snapshotPruningHeight - error": {
|
||||
getFlushedPruningSnapshotHeights: func() *list.List {
|
||||
l := list.New()
|
||||
l.PushBack(int64(5))
|
||||
l.PushBack(int64(-2))
|
||||
l.PushBack(int64(3))
|
||||
return l
|
||||
},
|
||||
expectedResult: &pruning.NegativeHeightsError{Height: -2},
|
||||
},
|
||||
"both have negative - pruningHeight error": {
|
||||
flushedPruningHeights: []int64{10, 0, -1},
|
||||
getFlushedPruningSnapshotHeights: func() *list.List {
|
||||
l := list.New()
|
||||
l.PushBack(int64(5))
|
||||
l.PushBack(int64(-2))
|
||||
l.PushBack(int64(3))
|
||||
return l
|
||||
},
|
||||
expectedResult: &pruning.NegativeHeightsError{Height: -1},
|
||||
},
|
||||
"both non-negative - success": {
|
||||
flushedPruningHeights: []int64{10, 0, 3},
|
||||
getFlushedPruningSnapshotHeights: func() *list.List {
|
||||
l := list.New()
|
||||
l.PushBack(int64(5))
|
||||
l.PushBack(int64(0))
|
||||
l.PushBack(int64(3))
|
||||
return l
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
db := db.NewMemDB()
|
||||
if tc.flushedPruningHeights != nil {
|
||||
err = db.Set(pruning.PruneHeightsKey, pruning.Int64SliceToBytes(tc.flushedPruningHeights))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if tc.getFlushedPruningSnapshotHeights != nil {
|
||||
err = db.Set(pruning.PruneSnapshotHeightsKey, pruning.ListToBytes(tc.getFlushedPruningSnapshotHeights()))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = manager.LoadPruningHeights(db)
|
||||
require.Equal(t, tc.expectedResult, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPruningHeights_PruneNothing(t *testing.T) {
|
||||
var manager = pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
|
||||
require.NotNil(t, manager)
|
||||
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningNothing))
|
||||
|
||||
require.Nil(t, manager.LoadPruningHeights(db.NewMemDB()))
|
||||
}
|
||||
|
||||
func TestGetFlushAndResetPruningHeights_DbErr_Panic(t *testing.T) {
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
// Setup
|
||||
dbMock := mock.NewMockDB(ctrl)
|
||||
|
||||
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
|
||||
|
||||
manager := pruning.NewManager(dbMock, log.NewNopLogger())
|
||||
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
|
||||
require.NotNil(t, manager)
|
||||
|
||||
heights, err := manager.GetFlushAndResetPruningHeights()
|
||||
require.Error(t, err)
|
||||
require.Nil(t, heights)
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: /home/roman/projects/cosmos-sdk/vendor/github.com/tendermint/tm-db/types.go
|
||||
|
||||
// Package mock_db is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
db "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
// MockDB is a mock of DB interface.
|
||||
type MockDB struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDBMockRecorder
|
||||
}
|
||||
|
||||
// MockDBMockRecorder is the mock recorder for MockDB.
|
||||
type MockDBMockRecorder struct {
|
||||
mock *MockDB
|
||||
}
|
||||
|
||||
// NewMockDB creates a new mock instance.
|
||||
func NewMockDB(ctrl *gomock.Controller) *MockDB {
|
||||
mock := &MockDB{ctrl: ctrl}
|
||||
mock.recorder = &MockDBMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockDB) EXPECT() *MockDBMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockDB) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockDBMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDB)(nil).Close))
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockDB) Delete(arg0 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockDBMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDB)(nil).Delete), arg0)
|
||||
}
|
||||
|
||||
// DeleteSync mocks base method.
|
||||
func (m *MockDB) DeleteSync(arg0 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteSync", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteSync indicates an expected call of DeleteSync.
|
||||
func (mr *MockDBMockRecorder) DeleteSync(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSync", reflect.TypeOf((*MockDB)(nil).DeleteSync), arg0)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockDB) Get(arg0 []byte) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockDBMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// Has mocks base method.
|
||||
func (m *MockDB) Has(key []byte) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Has", key)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Has indicates an expected call of Has.
|
||||
func (mr *MockDBMockRecorder) Has(key interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockDB)(nil).Has), key)
|
||||
}
|
||||
|
||||
// Iterator mocks base method.
|
||||
func (m *MockDB) Iterator(start, end []byte) (db.Iterator, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Iterator", start, end)
|
||||
ret0, _ := ret[0].(db.Iterator)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Iterator indicates an expected call of Iterator.
|
||||
func (mr *MockDBMockRecorder) Iterator(start, end interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterator", reflect.TypeOf((*MockDB)(nil).Iterator), start, end)
|
||||
}
|
||||
|
||||
// NewBatch mocks base method.
|
||||
func (m *MockDB) NewBatch() db.Batch {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "NewBatch")
|
||||
ret0, _ := ret[0].(db.Batch)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// NewBatch indicates an expected call of NewBatch.
|
||||
func (mr *MockDBMockRecorder) NewBatch() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBatch", reflect.TypeOf((*MockDB)(nil).NewBatch))
|
||||
}
|
||||
|
||||
// Print mocks base method.
|
||||
func (m *MockDB) Print() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Print")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Print indicates an expected call of Print.
|
||||
func (mr *MockDBMockRecorder) Print() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Print", reflect.TypeOf((*MockDB)(nil).Print))
|
||||
}
|
||||
|
||||
// ReverseIterator mocks base method.
|
||||
func (m *MockDB) ReverseIterator(start, end []byte) (db.Iterator, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ReverseIterator", start, end)
|
||||
ret0, _ := ret[0].(db.Iterator)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ReverseIterator indicates an expected call of ReverseIterator.
|
||||
func (mr *MockDBMockRecorder) ReverseIterator(start, end interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReverseIterator", reflect.TypeOf((*MockDB)(nil).ReverseIterator), start, end)
|
||||
}
|
||||
|
||||
// Set mocks base method.
|
||||
func (m *MockDB) Set(arg0, arg1 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set.
|
||||
func (mr *MockDBMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockDB)(nil).Set), arg0, arg1)
|
||||
}
|
||||
|
||||
// SetSync mocks base method.
|
||||
func (m *MockDB) SetSync(arg0, arg1 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetSync", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetSync indicates an expected call of SetSync.
|
||||
func (mr *MockDBMockRecorder) SetSync(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSync", reflect.TypeOf((*MockDB)(nil).SetSync), arg0, arg1)
|
||||
}
|
||||
|
||||
// Stats mocks base method.
|
||||
func (m *MockDB) Stats() map[string]string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Stats")
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Stats indicates an expected call of Stats.
|
||||
func (mr *MockDBMockRecorder) Stats() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockDB)(nil).Stats))
|
||||
}
|
||||
|
||||
// MockBatch is a mock of Batch interface.
|
||||
type MockBatch struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockBatchMockRecorder
|
||||
}
|
||||
|
||||
// MockBatchMockRecorder is the mock recorder for MockBatch.
|
||||
type MockBatchMockRecorder struct {
|
||||
mock *MockBatch
|
||||
}
|
||||
|
||||
// NewMockBatch creates a new mock instance.
|
||||
func NewMockBatch(ctrl *gomock.Controller) *MockBatch {
|
||||
mock := &MockBatch{ctrl: ctrl}
|
||||
mock.recorder = &MockBatchMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockBatch) EXPECT() *MockBatchMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockBatch) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockBatchMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockBatch)(nil).Close))
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockBatch) Delete(key []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", key)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockBatchMockRecorder) Delete(key interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBatch)(nil).Delete), key)
|
||||
}
|
||||
|
||||
// Set mocks base method.
|
||||
func (m *MockBatch) Set(key, value []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Set", key, value)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set.
|
||||
func (mr *MockBatchMockRecorder) Set(key, value interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockBatch)(nil).Set), key, value)
|
||||
}
|
||||
|
||||
// Write mocks base method.
|
||||
func (m *MockBatch) Write() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Write")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Write indicates an expected call of Write.
|
||||
func (mr *MockBatchMockRecorder) Write() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockBatch)(nil).Write))
|
||||
}
|
||||
|
||||
// WriteSync mocks base method.
|
||||
func (m *MockBatch) WriteSync() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "WriteSync")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// WriteSync indicates an expected call of WriteSync.
|
||||
func (mr *MockBatchMockRecorder) WriteSync() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteSync", reflect.TypeOf((*MockBatch)(nil).WriteSync))
|
||||
}
|
||||
|
||||
// MockIterator is a mock of Iterator interface.
|
||||
type MockIterator struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIteratorMockRecorder
|
||||
}
|
||||
|
||||
// MockIteratorMockRecorder is the mock recorder for MockIterator.
|
||||
type MockIteratorMockRecorder struct {
|
||||
mock *MockIterator
|
||||
}
|
||||
|
||||
// NewMockIterator creates a new mock instance.
|
||||
func NewMockIterator(ctrl *gomock.Controller) *MockIterator {
|
||||
mock := &MockIterator{ctrl: ctrl}
|
||||
mock.recorder = &MockIteratorMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockIterator) EXPECT() *MockIteratorMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockIterator) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockIteratorMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIterator)(nil).Close))
|
||||
}
|
||||
|
||||
// Domain mocks base method.
|
||||
func (m *MockIterator) Domain() ([]byte, []byte) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Domain")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].([]byte)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Domain indicates an expected call of Domain.
|
||||
func (mr *MockIteratorMockRecorder) Domain() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Domain", reflect.TypeOf((*MockIterator)(nil).Domain))
|
||||
}
|
||||
|
||||
// Error mocks base method.
|
||||
func (m *MockIterator) Error() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Error")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockIteratorMockRecorder) Error() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockIterator)(nil).Error))
|
||||
}
|
||||
|
||||
// Key mocks base method.
|
||||
func (m *MockIterator) Key() []byte {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Key")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Key indicates an expected call of Key.
|
||||
func (mr *MockIteratorMockRecorder) Key() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockIterator)(nil).Key))
|
||||
}
|
||||
|
||||
// Next mocks base method.
|
||||
func (m *MockIterator) Next() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Next")
|
||||
}
|
||||
|
||||
// Next indicates an expected call of Next.
|
||||
func (mr *MockIteratorMockRecorder) Next() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Next", reflect.TypeOf((*MockIterator)(nil).Next))
|
||||
}
|
||||
|
||||
// Valid mocks base method.
|
||||
func (m *MockIterator) Valid() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Valid")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Valid indicates an expected call of Valid.
|
||||
func (mr *MockIteratorMockRecorder) Valid() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Valid", reflect.TypeOf((*MockIterator)(nil).Valid))
|
||||
}
|
||||
|
||||
// Value mocks base method.
|
||||
func (m *MockIterator) Value() []byte {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Value")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Value indicates an expected call of Value.
|
||||
func (mr *MockIteratorMockRecorder) Value() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockIterator)(nil).Value))
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// PruningOptions defines the pruning strategy used when determining which
|
||||
// heights are removed from disk when committing state.
|
||||
type PruningOptions struct {
|
||||
// KeepRecent defines how many recent heights to keep on disk.
|
||||
KeepRecent uint64
|
||||
|
||||
// Interval defines when the pruned heights are removed from disk.
|
||||
Interval uint64
|
||||
|
||||
// Strategy defines the kind of pruning strategy. See below for more information on each.
|
||||
Strategy PruningStrategy
|
||||
}
|
||||
|
||||
type PruningStrategy int
|
||||
|
||||
// Pruning option string constants
|
||||
const (
|
||||
PruningOptionDefault = "default"
|
||||
PruningOptionEverything = "everything"
|
||||
PruningOptionNothing = "nothing"
|
||||
PruningOptionCustom = "custom"
|
||||
)
|
||||
|
||||
const (
|
||||
// PruningDefault defines a pruning strategy where the last 362880 heights are
|
||||
// kept where to-be pruned heights are pruned at every 10th height.
|
||||
// The last 362880 heights are kept(approximately 3.5 weeks worth of state) assuming the typical
|
||||
// block time is 6s. If these values do not match the applications' requirements, use the "custom" option.
|
||||
PruningDefault PruningStrategy = iota
|
||||
// PruningEverything defines a pruning strategy where all committed heights are
|
||||
// deleted, storing only the current height and last 2 states. To-be pruned heights are
|
||||
// pruned at every 10th height.
|
||||
PruningEverything
|
||||
// PruningNothing defines a pruning strategy where all heights are kept on disk.
|
||||
// This is the only stretegy where KeepEvery=1 is allowed with state-sync snapshots disabled.
|
||||
PruningNothing
|
||||
// PruningCustom defines a pruning strategy where the user specifies the pruning.
|
||||
PruningCustom
|
||||
// PruningUndefined defines an undefined pruning strategy. It is to be returned by stores that do not support pruning.
|
||||
PruningUndefined
|
||||
)
|
||||
|
||||
const (
|
||||
pruneEverythingKeepRecent = 2
|
||||
pruneEverythingInterval = 10
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPruningIntervalZero = errors.New("'pruning-interval' must not be 0. If you want to disable pruning, select pruning = \"nothing\"")
|
||||
ErrPruningIntervalTooSmall = fmt.Errorf("'pruning-interval' must not be less than %d. For the most aggressive pruning, select pruning = \"everything\"", pruneEverythingInterval)
|
||||
ErrPruningKeepRecentTooSmall = fmt.Errorf("'pruning-keep-recent' must not be less than %d. For the most aggressive pruning, select pruning = \"everything\"", pruneEverythingKeepRecent)
|
||||
)
|
||||
|
||||
func NewPruningOptions(pruningStrategy PruningStrategy) PruningOptions {
|
||||
switch pruningStrategy {
|
||||
case PruningDefault:
|
||||
return PruningOptions{
|
||||
KeepRecent: 362880,
|
||||
Interval: 10,
|
||||
Strategy: PruningDefault,
|
||||
}
|
||||
case PruningEverything:
|
||||
return PruningOptions{
|
||||
KeepRecent: pruneEverythingKeepRecent,
|
||||
Interval: pruneEverythingInterval,
|
||||
Strategy: PruningEverything,
|
||||
}
|
||||
case PruningNothing:
|
||||
return PruningOptions{
|
||||
KeepRecent: 0,
|
||||
Interval: 0,
|
||||
Strategy: PruningNothing,
|
||||
}
|
||||
default:
|
||||
return PruningOptions{
|
||||
Strategy: PruningCustom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewCustomPruningOptions(keepRecent, interval uint64) PruningOptions {
|
||||
return PruningOptions{
|
||||
KeepRecent: keepRecent,
|
||||
Interval: interval,
|
||||
Strategy: PruningCustom,
|
||||
}
|
||||
}
|
||||
|
||||
func (po PruningOptions) GetPruningStrategy() PruningStrategy {
|
||||
return po.Strategy
|
||||
}
|
||||
|
||||
func (po PruningOptions) Validate() error {
|
||||
if po.Strategy == PruningNothing {
|
||||
return nil
|
||||
}
|
||||
if po.Interval == 0 {
|
||||
return ErrPruningIntervalZero
|
||||
}
|
||||
if po.Interval < pruneEverythingInterval {
|
||||
return ErrPruningIntervalTooSmall
|
||||
}
|
||||
if po.KeepRecent < pruneEverythingKeepRecent {
|
||||
return ErrPruningKeepRecentTooSmall
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPruningOptionsFromString(strategy string) PruningOptions {
|
||||
switch strategy {
|
||||
case PruningOptionEverything:
|
||||
return NewPruningOptions(PruningEverything)
|
||||
|
||||
case PruningOptionNothing:
|
||||
return NewPruningOptions(PruningNothing)
|
||||
|
||||
case PruningOptionDefault:
|
||||
return NewPruningOptions(PruningDefault)
|
||||
|
||||
default:
|
||||
return NewPruningOptions(PruningDefault)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPruningOptions_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
opts PruningOptions
|
||||
expectErr error
|
||||
}{
|
||||
{NewPruningOptions(PruningDefault), nil},
|
||||
{NewPruningOptions(PruningEverything), nil},
|
||||
{NewPruningOptions(PruningNothing), nil},
|
||||
{NewPruningOptions(PruningCustom), ErrPruningIntervalZero},
|
||||
{NewCustomPruningOptions(2, 10), nil},
|
||||
{NewCustomPruningOptions(100, 15), nil},
|
||||
{NewCustomPruningOptions(1, 10), ErrPruningKeepRecentTooSmall},
|
||||
{NewCustomPruningOptions(2, 9), ErrPruningIntervalTooSmall},
|
||||
{NewCustomPruningOptions(2, 0), ErrPruningIntervalZero},
|
||||
{NewCustomPruningOptions(2, 0), ErrPruningIntervalZero},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := tc.opts.Validate()
|
||||
require.Equal(t, tc.expectErr, err, "options: %v, err: %s", tc.opts, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruningOptions_GetStrategy(t *testing.T) {
|
||||
testCases := []struct {
|
||||
opts PruningOptions
|
||||
expectedStrategy PruningStrategy
|
||||
}{
|
||||
{NewPruningOptions(PruningDefault), PruningDefault},
|
||||
{NewPruningOptions(PruningEverything), PruningEverything},
|
||||
{NewPruningOptions(PruningNothing), PruningNothing},
|
||||
{NewPruningOptions(PruningCustom), PruningCustom},
|
||||
{NewCustomPruningOptions(2, 10), PruningCustom},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
actualStrategy := tc.opts.GetPruningStrategy()
|
||||
require.Equal(t, tc.expectedStrategy, actualStrategy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPruningOptionsFromString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
optString string
|
||||
expect PruningOptions
|
||||
}{
|
||||
{PruningOptionDefault, NewPruningOptions(PruningDefault)},
|
||||
{PruningOptionEverything, NewPruningOptions(PruningEverything)},
|
||||
{PruningOptionNothing, NewPruningOptions(PruningNothing)},
|
||||
{"invalid", NewPruningOptions(PruningDefault)},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
actual := NewPruningOptionsFromString(tc.optString)
|
||||
require.Equal(t, tc.expect, actual)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user