From 49a98ad4b610cc0025ced15b1187e8f236fc0751 Mon Sep 17 00:00:00 2001
From: Alexandr Borodulin <sashaborodulin@gmail.com>
Date: Fri, 17 Sep 2021 15:01:15 +0300
Subject: [PATCH] Add prune before parameters

---
 cmd/integration/commands/flags.go    |   2 +
 cmd/integration/commands/stages.go   |  15 +-
 eth/ethconfig/gen_config.go          |   7 +-
 eth/stagedsync/stage_execute_test.go |   8 +-
 ethdb/prune/storage_mode.go          | 207 +++++++++++++++++++--------
 ethdb/prune/storage_mode_test.go     |  58 +++++++-
 go.mod                               |   2 +-
 go.sum                               |   4 +-
 turbo/cli/default_flags.go           |   4 +
 turbo/cli/flags.go                   |  39 ++++-
 turbo/stages/blockchain_test.go      |   2 +-
 11 files changed, 268 insertions(+), 80 deletions(-)

diff --git a/cmd/integration/commands/flags.go b/cmd/integration/commands/flags.go
index 76d2d67253..a3ed21b8eb 100644
--- a/cmd/integration/commands/flags.go
+++ b/cmd/integration/commands/flags.go
@@ -25,6 +25,8 @@ var (
 	txtrace                        bool // Whether to trace the execution (should only be used together eith `block`)
 	pruneFlag                      string
 	pruneH, pruneR, pruneT, pruneC uint64
+	pruneHBefore, pruneRBefore     uint64
+	pruneTBefore, pruneCBefore     uint64
 	experiments                    []string
 	chain                          string // Which chain to use (mainnet, ropsten, rinkeby, goerli, etc.)
 )
diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go
index 69d3c281b8..eb0cf6fee8 100644
--- a/cmd/integration/commands/stages.go
+++ b/cmd/integration/commands/stages.go
@@ -358,10 +358,14 @@ func init() {
 	withDatadir(cmdSetPrune)
 	withChain(cmdSetPrune)
 	cmdSetPrune.Flags().StringVar(&pruneFlag, "prune", "hrtc", "")
-	cmdSetPrune.Flags().Uint64Var(&pruneH, "--prune.h.older", 0, "")
-	cmdSetPrune.Flags().Uint64Var(&pruneR, "--prune.r.older", 0, "")
-	cmdSetPrune.Flags().Uint64Var(&pruneT, "--prune.t.older", 0, "")
-	cmdSetPrune.Flags().Uint64Var(&pruneC, "--prune.c.older", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneH, "prune.h.older", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneR, "prune.r.older", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneT, "prune.t.older", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneC, "prune.c.older", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneHBefore, "prune.h.before", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneRBefore, "prune.r.before", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneTBefore, "prune.t.before", 0, "")
+	cmdSetPrune.Flags().Uint64Var(&pruneCBefore, "prune.c.before", 0, "")
 	cmdSetPrune.Flags().StringSliceVar(&experiments, "experiments", nil, "Storage mode to override database")
 	rootCmd.AddCommand(cmdSetPrune)
 }
@@ -993,7 +997,8 @@ func stage(st *stagedsync.Sync, tx kv.Tx, db kv.RoDB, stage stages.SyncStage) *s
 }
 
 func overrideStorageMode(db kv.RwDB) error {
-	pm, err := prune.FromCli(pruneFlag, pruneH, pruneR, pruneT, pruneC, experiments)
+	pm, err := prune.FromCli(pruneFlag, pruneH, pruneR, pruneT, pruneC,
+		pruneHBefore, pruneRBefore, pruneTBefore, pruneCBefore, experiments)
 	if err != nil {
 		return err
 	}
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index 7ef951273b..baa9d077b7 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -54,6 +54,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		PruneR             *uint64
 		PruneT             *uint64
 		PruneC             *uint64
+		PruneBeforeH       *uint64
+		PruneBeforeR       *uint64
+		PruneBeforeT       *uint64
+		PruneBeforeC       *uint64
 		Experiments        *[]string
 		OnlyAnnounce       *bool
 		SkipBcVersionCheck *bool `toml:"-"`
@@ -83,7 +87,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		c.Whitelist = dec.Whitelist
 	}
 	if dec.Mode != nil {
-		mode, err := prune.FromCli(*dec.Mode, *dec.PruneH, *dec.PruneR, *dec.PruneT, *dec.PruneC, *dec.Experiments)
+		mode, err := prune.FromCli(*dec.Mode, *dec.PruneH, *dec.PruneR, *dec.PruneT, *dec.PruneC,
+			*dec.PruneBeforeH, *dec.PruneBeforeR, *dec.PruneBeforeT, *dec.PruneBeforeC, *dec.Experiments)
 		if err != nil {
 			return err
 		}
diff --git a/eth/stagedsync/stage_execute_test.go b/eth/stagedsync/stage_execute_test.go
index b50cfb1571..10f64653c8 100644
--- a/eth/stagedsync/stage_execute_test.go
+++ b/eth/stagedsync/stage_execute_test.go
@@ -87,7 +87,7 @@ func TestPruneExecution(t *testing.T) {
 
 	s := &PruneState{ID: stages.Execution, ForwardProgress: 50}
 	// check pruning distance > than current stage progress
-	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: 100, Receipts: 101, CallTraces: 200}}, ctx, false)
+	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: prune.Distance(100), Receipts: prune.Distance(101), CallTraces: prune.Distance(200)}}, ctx, false)
 	assert.NoError(err)
 
 	available, err = changeset.AvailableFrom(tx)
@@ -98,7 +98,8 @@ func TestPruneExecution(t *testing.T) {
 	assert.Equal(uint64(1), available)
 
 	// pruning distance, first run
-	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: 5, Receipts: 15, CallTraces: 25}}, ctx, false)
+	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: prune.Distance(5),
+		Receipts: prune.Distance(15), CallTraces: prune.Distance(25)}}, ctx, false)
 	assert.NoError(err)
 
 	available, err = changeset.AvailableFrom(tx)
@@ -109,7 +110,8 @@ func TestPruneExecution(t *testing.T) {
 	assert.Equal(uint64(45), available)
 
 	// pruning distance, second run
-	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: 5, Receipts: 15, CallTraces: 25}}, ctx, false)
+	err = PruneExecutionStage(s, tx, ExecuteBlockCfg{prune: prune.Mode{History: prune.Distance(5),
+		Receipts: prune.Distance(15), CallTraces: prune.Distance(25)}}, ctx, false)
 	assert.NoError(err)
 
 	available, err = changeset.AvailableFrom(tx)
diff --git a/ethdb/prune/storage_mode.go b/ethdb/prune/storage_mode.go
index 2ab2930c6a..1042e3c25c 100644
--- a/ethdb/prune/storage_mode.go
+++ b/ethdb/prune/storage_mode.go
@@ -11,10 +11,10 @@ import (
 
 var DefaultMode = Mode{
 	Initialised: true,
-	History:     math.MaxUint64, // all off
-	Receipts:    math.MaxUint64,
-	TxIndex:     math.MaxUint64,
-	CallTraces:  math.MaxUint64,
+	History:     Distance(math.MaxUint64), // all off
+	Receipts:    Distance(math.MaxUint64),
+	TxIndex:     Distance(math.MaxUint64),
+	CallTraces:  Distance(math.MaxUint64),
 	Experiments: Experiments{}, // all off
 }
 
@@ -22,7 +22,8 @@ type Experiments struct {
 	TEVM bool
 }
 
-func FromCli(flags string, exactHistory, exactReceipts, exactTxIndex, exactCallTraces uint64, experiments []string) (Mode, error) {
+func FromCli(flags string, exactHistory, exactReceipts, exactTxIndex, exactCallTraces,
+	beforeH, beforeR, beforeT, beforeC uint64, experiments []string) (Mode, error) {
 	mode := DefaultMode
 	if flags == "default" || flags == "disabled" {
 		return DefaultMode, nil
@@ -31,13 +32,13 @@ func FromCli(flags string, exactHistory, exactReceipts, exactTxIndex, exactCallT
 	for _, flag := range flags {
 		switch flag {
 		case 'h':
-			mode.History = params.FullImmutabilityThreshold
+			mode.History = Distance(params.FullImmutabilityThreshold)
 		case 'r':
-			mode.Receipts = params.FullImmutabilityThreshold
+			mode.Receipts = Distance(params.FullImmutabilityThreshold)
 		case 't':
-			mode.TxIndex = params.FullImmutabilityThreshold
+			mode.TxIndex = Distance(params.FullImmutabilityThreshold)
 		case 'c':
-			mode.CallTraces = params.FullImmutabilityThreshold
+			mode.CallTraces = Distance(params.FullImmutabilityThreshold)
 		default:
 			return DefaultMode, fmt.Errorf("unexpected flag found: %c", flag)
 		}
@@ -56,6 +57,19 @@ func FromCli(flags string, exactHistory, exactReceipts, exactTxIndex, exactCallT
 		mode.CallTraces = Distance(exactCallTraces)
 	}
 
+	if beforeH > 0 {
+		mode.History = Before(beforeH)
+	}
+	if beforeR > 0 {
+		mode.Receipts = Before(beforeR)
+	}
+	if beforeT > 0 {
+		mode.TxIndex = Before(beforeT)
+	}
+	if beforeC > 0 {
+		mode.CallTraces = Before(beforeC)
+	}
+
 	for _, ex := range experiments {
 		switch ex {
 		case "tevm":
@@ -74,61 +88,64 @@ func Get(db kv.Getter) (Mode, error) {
 	prune := DefaultMode
 	prune.Initialised = true
 
-	v, err := db.GetOne(kv.DatabaseInfo, kv.PruneDistanceHistory)
+	blockAmount, err := get(db, kv.PruneHistory)
 	if err != nil {
 		return prune, err
 	}
-	if v != nil {
-		prune.History = Distance(binary.BigEndian.Uint64(v))
-	} else {
-		prune.History = math.MaxUint64
+	if blockAmount != nil {
+		prune.History = blockAmount
 	}
-	v, err = db.GetOne(kv.DatabaseInfo, kv.PruneDistanceReceipts)
+
+	blockAmount, err = get(db, kv.PruneReceipts)
 	if err != nil {
 		return prune, err
 	}
-	if v != nil {
-		prune.Receipts = Distance(binary.BigEndian.Uint64(v))
-	} else {
-		prune.Receipts = math.MaxUint64
+	if blockAmount != nil {
+		prune.Receipts = blockAmount
 	}
-	v, err = db.GetOne(kv.DatabaseInfo, kv.PruneDistanceTxIndex)
+
+	blockAmount, err = get(db, kv.PruneTxIndex)
 	if err != nil {
 		return prune, err
 	}
-	if v != nil {
-		prune.TxIndex = Distance(binary.BigEndian.Uint64(v))
-	} else {
-		prune.TxIndex = math.MaxUint64
+	if blockAmount != nil {
+		prune.TxIndex = blockAmount
 	}
 
-	v, err = db.GetOne(kv.DatabaseInfo, kv.PruneDistanceCallTraces)
+	blockAmount, err = get(db, kv.PruneCallTraces)
 	if err != nil {
 		return prune, err
 	}
-	if v != nil {
-		prune.CallTraces = Distance(binary.BigEndian.Uint64(v))
-	} else {
-		prune.CallTraces = math.MaxUint64
+	if blockAmount != nil {
+		prune.CallTraces = blockAmount
 	}
 
-	v, err = db.GetOne(kv.DatabaseInfo, kv.StorageModeTEVM)
+	v, err := db.GetOne(kv.DatabaseInfo, kv.StorageModeTEVM)
 	if err != nil {
 		return prune, err
 	}
 	prune.Experiments.TEVM = len(v) == 1 && v[0] == 1
+
 	return prune, nil
 }
 
 type Mode struct {
 	Initialised bool // Set when the values are initialised (not default)
-	History     Distance
-	Receipts    Distance
-	TxIndex     Distance
-	CallTraces  Distance
+	History     BlockAmount
+	Receipts    BlockAmount
+	TxIndex     BlockAmount
+	CallTraces  BlockAmount
 	Experiments Experiments
 }
 
+type BlockAmount interface {
+	PruneTo(stageHead uint64) uint64
+	Enabled() bool
+	toValue() uint64
+	dbType() []byte
+	useDefaultValue() bool
+}
+
 // Distance amount of blocks to keep in DB
 // but manual manipulation with such distance is very unsafe
 // for example:
@@ -136,7 +153,10 @@ type Mode struct {
 // may delete whole db - because of uint64 underflow when pruningDistance > currentStageProgress
 type Distance uint64
 
-func (p Distance) Enabled() bool { return p != math.MaxUint64 }
+func (p Distance) Enabled() bool         { return p != math.MaxUint64 }
+func (p Distance) toValue() uint64       { return uint64(p) }
+func (p Distance) useDefaultValue() bool { return uint64(p) == params.FullImmutabilityThreshold }
+func (p Distance) dbType() []byte        { return kv.PruneTypeOlder }
 
 func (p Distance) PruneTo(stageHead uint64) uint64 {
 	if p == 0 {
@@ -148,6 +168,22 @@ func (p Distance) PruneTo(stageHead uint64) uint64 {
 	return stageHead - uint64(p)
 }
 
+// Before number after which keep in DB
+type Before uint64
+
+func (b Before) Enabled() bool         { return b > 0 }
+func (b Before) toValue() uint64       { return uint64(b) }
+func (b Before) useDefaultValue() bool { return uint64(b) == 0 }
+func (b Before) dbType() []byte        { return kv.PruneTypeBefore }
+
+func (b Before) PruneTo(uint64) uint64 {
+	if b == 0 {
+		return uint64(b)
+	}
+
+	return uint64(b) - 1
+}
+
 func (m Mode) String() string {
 	if !m.Initialised {
 		return "default"
@@ -155,31 +191,31 @@ func (m Mode) String() string {
 	long := ""
 	short := "--prune="
 	if m.History.Enabled() {
-		if m.History == params.FullImmutabilityThreshold {
+		if m.History.useDefaultValue() {
 			short += "h"
 		} else {
-			long += fmt.Sprintf(" --prune.h.older=%d", m.History)
+			long += fmt.Sprintf(" --prune.h.%s=%d", m.History.dbType(), m.History.toValue())
 		}
 	}
 	if m.Receipts.Enabled() {
-		if m.Receipts == params.FullImmutabilityThreshold {
+		if m.Receipts.useDefaultValue() {
 			short += "r"
 		} else {
-			long += fmt.Sprintf(" --prune.r.older=%d", m.Receipts)
+			long += fmt.Sprintf(" --prune.r.%s=%d", m.Receipts.dbType(), m.Receipts.toValue())
 		}
 	}
 	if m.TxIndex.Enabled() {
-		if m.TxIndex == params.FullImmutabilityThreshold {
+		if m.TxIndex.useDefaultValue() {
 			short += "t"
 		} else {
-			long += fmt.Sprintf(" --prune.t.older=%d", m.TxIndex)
+			long += fmt.Sprintf(" --prune.t.%s=%d", m.TxIndex.dbType(), m.TxIndex.toValue())
 		}
 	}
 	if m.CallTraces.Enabled() {
-		if m.CallTraces == params.FullImmutabilityThreshold {
+		if m.CallTraces.useDefaultValue() {
 			short += "c"
 		} else {
-			long += fmt.Sprintf(" --prune.c.older=%d", m.CallTraces)
+			long += fmt.Sprintf(" --prune.c.%s=%d", m.CallTraces.dbType(), m.CallTraces.toValue())
 		}
 	}
 	if m.Experiments.TEVM {
@@ -193,22 +229,22 @@ func Override(db kv.RwTx, sm Mode) error {
 		err error
 	)
 
-	err = setDistance(db, kv.PruneDistanceHistory, sm.History)
+	err = set(db, kv.PruneHistory, sm.History)
 	if err != nil {
 		return err
 	}
 
-	err = setDistance(db, kv.PruneDistanceReceipts, sm.Receipts)
+	err = set(db, kv.PruneReceipts, sm.Receipts)
 	if err != nil {
 		return err
 	}
 
-	err = setDistance(db, kv.PruneDistanceTxIndex, sm.TxIndex)
+	err = set(db, kv.PruneTxIndex, sm.TxIndex)
 	if err != nil {
 		return err
 	}
 
-	err = setDistance(db, kv.PruneDistanceCallTraces, sm.CallTraces)
+	err = set(db, kv.PruneCallTraces, sm.CallTraces)
 	if err != nil {
 		return err
 	}
@@ -229,54 +265,99 @@ func SetIfNotExist(db kv.GetPut, pm Mode) error {
 		pm = DefaultMode
 	}
 
-	err = setDistanceOnEmpty(db, kv.PruneDistanceHistory, pm.History)
-	if err != nil {
-		return err
+	pruneDBData := map[string]BlockAmount{
+		string(kv.PruneHistory):    pm.History,
+		string(kv.PruneReceipts):   pm.Receipts,
+		string(kv.PruneTxIndex):    pm.TxIndex,
+		string(kv.PruneCallTraces): pm.CallTraces,
 	}
 
-	err = setDistanceOnEmpty(db, kv.PruneDistanceReceipts, pm.Receipts)
-	if err != nil {
-		return err
+	for key, value := range pruneDBData {
+		err = setOnEmpty(db, []byte(key), value)
+		if err != nil {
+			return err
+		}
 	}
 
-	err = setDistanceOnEmpty(db, kv.PruneDistanceTxIndex, pm.TxIndex)
+	err = setModeOnEmpty(db, kv.StorageModeTEVM, pm.Experiments.TEVM)
 	if err != nil {
 		return err
 	}
 
-	err = setDistanceOnEmpty(db, kv.PruneDistanceCallTraces, pm.CallTraces)
+	return nil
+}
+
+func createBlockAmount(pruneType []byte, v []byte) (BlockAmount, error) {
+	var blockAmount BlockAmount
+
+	switch string(pruneType) {
+	case string(kv.PruneTypeOlder):
+		blockAmount = Distance(binary.BigEndian.Uint64(v))
+	case string(kv.PruneTypeBefore):
+		blockAmount = Before(binary.BigEndian.Uint64(v))
+	default:
+		return nil, fmt.Errorf("unexpected block amount type: %s", string(pruneType))
+	}
+
+	return blockAmount, nil
+}
+
+func get(db kv.Getter, key []byte) (BlockAmount, error) {
+	v, err := db.GetOne(kv.DatabaseInfo, key)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
-	err = setModeOnEmpty(db, kv.StorageModeTEVM, pm.Experiments.TEVM)
+	vType, err := db.GetOne(kv.DatabaseInfo, keyType(key))
 	if err != nil {
-		return err
+		return nil, err
 	}
 
-	return nil
+	if v != nil {
+		blockAmount, err := createBlockAmount(vType, v)
+		if err != nil {
+			return nil, err
+		}
+		return blockAmount, nil
+	}
+
+	return nil, nil
 }
 
-func setDistance(db kv.Putter, key []byte, distance Distance) error {
+func set(db kv.Putter, key []byte, blockAmount BlockAmount) error {
 	v := make([]byte, 8)
-	binary.BigEndian.PutUint64(v, uint64(distance))
+	binary.BigEndian.PutUint64(v, blockAmount.toValue())
 	if err := db.Put(kv.DatabaseInfo, key, v); err != nil {
 		return err
 	}
+
+	keyType := keyType(key)
+
+	if err := db.Put(kv.DatabaseInfo, keyType, blockAmount.dbType()); err != nil {
+		return err
+	}
+
 	return nil
 }
 
-func setDistanceOnEmpty(db kv.GetPut, key []byte, distance Distance) error {
+func keyType(name []byte) []byte {
+	return append(name, []byte("Type")...)
+}
+
+func setOnEmpty(db kv.GetPut, key []byte, blockAmount BlockAmount) error {
 	mode, err := db.GetOne(kv.DatabaseInfo, key)
 	if err != nil {
 		return err
 	}
 	if len(mode) == 0 || binary.BigEndian.Uint64(mode) == math.MaxUint64 {
 		v := make([]byte, 8)
-		binary.BigEndian.PutUint64(v, uint64(distance))
+		binary.BigEndian.PutUint64(v, blockAmount.toValue())
 		if err = db.Put(kv.DatabaseInfo, key, v); err != nil {
 			return err
 		}
+		if err = db.Put(kv.DatabaseInfo, keyType(key), blockAmount.dbType()); err != nil {
+			return err
+		}
 	}
 
 	return nil
diff --git a/ethdb/prune/storage_mode_test.go b/ethdb/prune/storage_mode_test.go
index d5fad13310..0df75df1c3 100644
--- a/ethdb/prune/storage_mode_test.go
+++ b/ethdb/prune/storage_mode_test.go
@@ -1,6 +1,8 @@
 package prune
 
 import (
+	"math/rand"
+	"strconv"
 	"testing"
 
 	"github.com/ledgerwatch/erigon-lib/kv/memdb"
@@ -12,12 +14,62 @@ func TestSetStorageModeIfNotExist(t *testing.T) {
 	_, tx := memdb.NewTestTx(t)
 	prune, err := Get(tx)
 	assert.NoError(t, err)
-	assert.Equal(t, Mode{true, math.MaxUint64, math.MaxUint64, math.MaxUint64, math.MaxUint64, Experiments{TEVM: false}}, prune)
+	assert.Equal(t, Mode{true, Distance(math.MaxUint64), Distance(math.MaxUint64),
+		Distance(math.MaxUint64), Distance(math.MaxUint64), Experiments{TEVM: false}}, prune)
 
-	err = SetIfNotExist(tx, Mode{true, 1, 2, 3, 4, Experiments{TEVM: false}})
+	err = SetIfNotExist(tx, Mode{true, Distance(1), Distance(2),
+		Before(3), Before(4), Experiments{TEVM: false}})
 	assert.NoError(t, err)
 
 	prune, err = Get(tx)
 	assert.NoError(t, err)
-	assert.Equal(t, Mode{true, 1, 2, 3, 4, Experiments{TEVM: false}}, prune)
+	assert.Equal(t, Mode{true, Distance(1), Distance(2),
+		Before(3), Before(4), Experiments{TEVM: false}}, prune)
+}
+
+var distanceTests = []struct {
+	stageHead uint64
+	pruneTo   uint64
+	expected  uint64
+}{
+	{3_000_000, 1, 2_999_999},
+	{3_000_000, 4_000_000, 0},
+	{3_000_000, math.MaxUint64, 0},
+	{3_000_000, 1_000_000, 2_000_000},
+}
+
+func TestDistancePruneTo(t *testing.T) {
+	for _, tt := range distanceTests {
+		t.Run(strconv.FormatUint(tt.pruneTo, 10), func(t *testing.T) {
+			stageHead := tt.stageHead
+			d := Distance(tt.pruneTo)
+			pruneTo := d.PruneTo(stageHead)
+
+			if pruneTo != tt.expected {
+				t.Errorf("got %d, want %d", pruneTo, tt.expected)
+			}
+		})
+	}
+}
+
+var beforeTests = []struct {
+	pruneTo  uint64
+	expected uint64
+}{
+	{0, 0},
+	{1_000_000, 999_999},
+}
+
+func TestBeforePruneTo(t *testing.T) {
+	for _, tt := range beforeTests {
+		t.Run(strconv.FormatUint(tt.pruneTo, 10), func(t *testing.T) {
+			stageHead := uint64(rand.Int63n(10_000_000))
+			b := Before(tt.pruneTo)
+			pruneTo := b.PruneTo(stageHead)
+
+			if pruneTo != tt.expected {
+				t.Errorf("got %d, want %d", pruneTo, tt.expected)
+			}
+		})
+	}
 }
diff --git a/go.mod b/go.mod
index e80bad4d85..fe6a1b96be 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	github.com/json-iterator/go v1.1.11
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kevinburke/go-bindata v3.21.0+incompatible
-	github.com/ledgerwatch/erigon-lib v0.0.0-20210917022838-527036f986bb
+	github.com/ledgerwatch/erigon-lib v0.0.0-20210917092859-d32bc94cf8c6
 	github.com/ledgerwatch/log/v3 v3.3.0
 	github.com/ledgerwatch/secp256k1 v0.0.0-20210626115225-cd5cd00ed72d
 	github.com/logrusorgru/aurora/v3 v3.0.0
diff --git a/go.sum b/go.sum
index ecefe2f02a..5772362141 100644
--- a/go.sum
+++ b/go.sum
@@ -492,8 +492,8 @@ github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3P
 github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
-github.com/ledgerwatch/erigon-lib v0.0.0-20210917022838-527036f986bb h1:RS3uSN0lx90yRRfihFRCSS7KH6LdXrVDC5E+MMTXVq4=
-github.com/ledgerwatch/erigon-lib v0.0.0-20210917022838-527036f986bb/go.mod h1:kZsi9wFAOYIkegoiSj10RXOVc0EmDtUxltnNP1f1ZE0=
+github.com/ledgerwatch/erigon-lib v0.0.0-20210917092859-d32bc94cf8c6 h1:62B+9leRW19FbZbnlVT8iQV91vglu7H8E9icCuSK1EY=
+github.com/ledgerwatch/erigon-lib v0.0.0-20210917092859-d32bc94cf8c6/go.mod h1:kZsi9wFAOYIkegoiSj10RXOVc0EmDtUxltnNP1f1ZE0=
 github.com/ledgerwatch/log/v3 v3.3.0 h1:k8N/3NQLILr8CKCMyza261vLFKU7VA+nMNNb0wVyQSc=
 github.com/ledgerwatch/log/v3 v3.3.0/go.mod h1:J58eOHHrIYHxl7LKkRsb/0YibKwtLfauUryl5SLRGm0=
 github.com/ledgerwatch/secp256k1 v0.0.0-20210626115225-cd5cd00ed72d h1:/IKMrJdfRsoYNc36PXqP4xMH3vhW/8IQyBKGQbKZUno=
diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go
index 640beac780..10e14ce4e4 100644
--- a/turbo/cli/default_flags.go
+++ b/turbo/cli/default_flags.go
@@ -28,6 +28,10 @@ var DefaultFlags = []cli.Flag{
 	PruneReceiptFlag,
 	PruneTxIndexFlag,
 	PruneCallTracesFlag,
+	PruneHistoryBeforeFlag,
+	PruneReceiptBeforeFlag,
+	PruneTxIndexBeforeFlag,
+	PruneCallTracesBeforeFlag,
 	SnapshotModeFlag,
 	SeedSnapshotsFlag,
 	SnapshotDatabaseLayoutFlag,
diff --git a/turbo/cli/flags.go b/turbo/cli/flags.go
index e704145ba0..9b7de765de 100644
--- a/turbo/cli/flags.go
+++ b/turbo/cli/flags.go
@@ -88,6 +88,24 @@ var (
 		Name:  "prune.c.older",
 		Usage: `Prune data after this amount of blocks (if --prune flag has 'c', then default is 90K)`,
 	}
+
+	PruneHistoryBeforeFlag = cli.Uint64Flag{
+		Name:  "prune.h.before",
+		Usage: `Prune data before this block`,
+	}
+	PruneReceiptBeforeFlag = cli.Uint64Flag{
+		Name:  "prune.r.before",
+		Usage: `Prune data before this block`,
+	}
+	PruneTxIndexBeforeFlag = cli.Uint64Flag{
+		Name:  "prune.t.before",
+		Usage: `Prune data before this block`,
+	}
+	PruneCallTracesBeforeFlag = cli.Uint64Flag{
+		Name:  "prune.c.before",
+		Usage: `Prune data before this block`,
+	}
+
 	ExperimentsFlag = cli.StringFlag{
 		Name: "experiments",
 		Usage: `Enable some experimental stages:
@@ -166,6 +184,10 @@ func ApplyFlagsForEthConfig(ctx *cli.Context, cfg *ethconfig.Config) {
 		ctx.GlobalUint64(PruneReceiptFlag.Name),
 		ctx.GlobalUint64(PruneTxIndexFlag.Name),
 		ctx.GlobalUint64(PruneCallTracesFlag.Name),
+		ctx.GlobalUint64(PruneHistoryBeforeFlag.Name),
+		ctx.GlobalUint64(PruneReceiptBeforeFlag.Name),
+		ctx.GlobalUint64(PruneTxIndexBeforeFlag.Name),
+		ctx.GlobalUint64(PruneCallTracesBeforeFlag.Name),
 		strings.Split(ctx.GlobalString(ExperimentsFlag.Name), ","),
 	)
 	if err != nil {
@@ -239,7 +261,22 @@ func ApplyFlagsForEthConfigCobra(f *pflag.FlagSet, cfg *ethconfig.Config) {
 		if v := f.Uint64(PruneCallTracesFlag.Name, PruneCallTracesFlag.Value, PruneCallTracesFlag.Usage); v != nil {
 			exactC = *v
 		}
-		mode, err := prune.FromCli(*v, exactH, exactR, exactT, exactC, experiments)
+
+		var beforeH, beforeR, beforeT, beforeC uint64
+		if v := f.Uint64(PruneHistoryBeforeFlag.Name, PruneHistoryBeforeFlag.Value, PruneHistoryBeforeFlag.Usage); v != nil {
+			beforeH = *v
+		}
+		if v := f.Uint64(PruneReceiptBeforeFlag.Name, PruneReceiptBeforeFlag.Value, PruneReceiptBeforeFlag.Usage); v != nil {
+			beforeR = *v
+		}
+		if v := f.Uint64(PruneTxIndexBeforeFlag.Name, PruneTxIndexBeforeFlag.Value, PruneTxIndexBeforeFlag.Usage); v != nil {
+			beforeT = *v
+		}
+		if v := f.Uint64(PruneCallTracesBeforeFlag.Name, PruneCallTracesBeforeFlag.Value, PruneCallTracesBeforeFlag.Usage); v != nil {
+			beforeC = *v
+		}
+
+		mode, err := prune.FromCli(*v, exactH, exactR, exactT, exactC, beforeH, beforeR, beforeT, beforeC, experiments)
 		if err != nil {
 			utils.Fatalf(fmt.Sprintf("error while parsing mode: %v", err))
 		}
diff --git a/turbo/stages/blockchain_test.go b/turbo/stages/blockchain_test.go
index 7da01ed603..92f7bb3301 100644
--- a/turbo/stages/blockchain_test.go
+++ b/turbo/stages/blockchain_test.go
@@ -837,7 +837,7 @@ func runPermutation(t *testing.T, testFunc func(*testing.T, prune.Mode) error, c
 	if err := runPermutation(t, testFunc, current+1, pm); err != nil {
 		return err
 	}
-	invert := func(a prune.Distance) prune.Distance {
+	invert := func(a prune.BlockAmount) prune.Distance {
 		if a.Enabled() {
 			return math.MaxUint64
 		}
-- 
GitLab