Skip to content

History expiry phase-1 rollout. (Drop Pre-merge data) #15668

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

Merged
merged 36 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions erigon-lib/chain/chain_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type Config struct {
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"` // The merge happens when terminal total difficulty is reached
TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"` // Disable PoW sync for networks that have already passed through the Merge
MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter; see FORK_NEXT_VALUE in EIP-3675
MergeHeight *big.Int `json:"mergeBlock,omitempty"` // The Merge block number

// Mainnet fork scheduling switched from block numbers to timestamps after The Merge
ShanghaiTime *big.Int `json:"shanghaiTime,omitempty"`
Expand Down Expand Up @@ -694,3 +695,7 @@ func isForked(s *big.Int, head uint64) bool {
}
return s.Uint64() <= head
}

func (c *Config) IsPreMerge(blockNumber uint64) bool {
return c.MergeHeight != nil && blockNumber < c.MergeHeight.Uint64()
}
3 changes: 2 additions & 1 deletion erigon-lib/common/dbg/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import (
)

var (
MaxReorgDepth = EnvInt("MAX_REORG_DEPTH", 512)
MaxReorgDepth = EnvInt("MAX_REORG_DEPTH", 512)
EnableHistoryExpiry = EnvBool("ENABLE_HISTORY_EXPIRY", false)

noMemstat = EnvBool("NO_MEMSTAT", false)
saveHeapProfile = EnvBool("SAVE_HEAP_PROFILE", false)
Expand Down
51 changes: 35 additions & 16 deletions erigon-lib/kv/prune/storage_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ var (
ArchiveMode = Mode{
Initialised: true,
History: Distance(math.MaxUint64),
Blocks: Distance(math.MaxUint64),
Blocks: KeepAllBlocksPruneMode,
}
FullMode = Mode{
Initialised: true,
Blocks: Distance(math.MaxUint64),
Blocks: DefaultBlocksPruneMode,
History: Distance(config3.DefaultPruneDistance),
}
BlocksMode = Mode{
Initialised: true,
Blocks: KeepAllBlocksPruneMode,
History: Distance(config3.DefaultPruneDistance),
}
MinimalMode = Mode{
Expand All @@ -46,13 +51,19 @@ var (
}

DefaultMode = ArchiveMode
MockMode = Mode{
Initialised: true,
History: Distance(math.MaxUint64),
Blocks: Distance(math.MaxUint64),
}

ErrUnknownPruneMode = fmt.Errorf("--prune.mode must be one of %s, %s, %s", archiveModeStr, fullModeStr, minimalModeStr)
ErrDistanceOnlyForArchive = fmt.Errorf("--prune.distance and --prune.distance.blocks are only allowed with --prune.mode=%s", archiveModeStr)
)

const (
archiveModeStr = "archive"
blockModeStr = "blocks"
fullModeStr = "full"
minimalModeStr = "minimal"
)
Expand All @@ -74,6 +85,10 @@ func (m Mode) String() string {
return minimalModeStr
}

if m.Blocks.toValue() == BlocksMode.Blocks.toValue() && m.History.toValue() == BlocksMode.History.toValue() {
return blockModeStr
}

short := archiveModeStr
if m.History.toValue() != DefaultMode.History.toValue() {
short += fmt.Sprintf(" --prune.distance=%d", m.History.toValue())
Expand All @@ -89,28 +104,21 @@ func FromCli(pruneMode string, distanceHistory, distanceBlocks uint64) (Mode, er
switch pruneMode {
case archiveModeStr, "":
mode = ArchiveMode
if distanceHistory > 0 {
mode.History = Distance(distanceHistory)
}
if distanceBlocks > 0 {
mode.Blocks = Distance(distanceBlocks)
}
case fullModeStr:
mode = FullMode
case minimalModeStr:
mode = MinimalMode
case blockModeStr:
mode = BlocksMode
default:
return Mode{}, ErrUnknownPruneMode
}

if pruneMode != archiveModeStr {
// Override is not allowed for full/minimal mode
if distanceHistory > 0 && distanceHistory != mode.History.toValue() {
return Mode{}, ErrDistanceOnlyForArchive
}
if distanceBlocks > 0 && distanceBlocks != mode.Blocks.toValue() {
return Mode{}, ErrDistanceOnlyForArchive
}
if distanceHistory > 0 {
mode.History = Distance(distanceHistory)
}
if distanceBlocks > 0 {
mode.Blocks = Distance(distanceBlocks)
}
return mode, nil
}
Expand Down Expand Up @@ -138,6 +146,11 @@ func Get(db kv.Getter) (Mode, error) {
return prune, nil
}

const (
DefaultBlocksPruneMode = Distance(math.MaxUint64) // Use chain-specific history pruning (aka. history-expiry)
KeepAllBlocksPruneMode = Distance(math.MaxUint64 - 1) // Keep all history
)

type BlockAmount interface {
PruneTo(stageHead uint64) uint64
Enabled() bool
Expand Down Expand Up @@ -179,6 +192,12 @@ func EnsureNotChanged(tx kv.GetPut, pruneMode Mode) (Mode, error) {
}

if pruneMode.Initialised {
// Little initial design flaw: we used maxUint64 as default value for prune distance so history expiry was not accounted for.
// We need to use because we are changing defaults in archive node from DefaultBlocksPruneMode to KeepAllBlocksPruneMode which is a different value so it would fail if we are running --prune.mode=archive.
if (pm.History == DefaultBlocksPruneMode && pruneMode.History == DefaultBlocksPruneMode) &&
(pm.Blocks == DefaultBlocksPruneMode && pruneMode.Blocks == KeepAllBlocksPruneMode) {
return pruneMode, nil
}
// If storage mode is not explicitly specified, we take whatever is in the database
if !reflect.DeepEqual(pm, pruneMode) {
return pm, errors.New("changing --prune.* flags is prohibited, last time you used: --prune.mode=" + pm.String())
Expand Down
4 changes: 0 additions & 4 deletions erigon-lib/kv/prune/storage_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ func TestParseCLIMode(t *testing.T) {
assert.Equal(t, MinimalMode, mode)
assert.Equal(t, minimalModeStr, mode.String())
})
t.Run("minimal_override", func(t *testing.T) {
_, err := FromCli(minimalModeStr, 1, 2)
assert.ErrorIs(t, err, ErrDistanceOnlyForArchive)
})
t.Run("garbage", func(t *testing.T) {
_, err := FromCli("garb", 1, 2)
assert.ErrorIs(t, err, ErrUnknownPruneMode)
Expand Down
2 changes: 1 addition & 1 deletion execution/stages/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ func doModesTest(t *testing.T, pm prune.Mode) error {
}

func runWithModesPermuations(t *testing.T, testFunc func(*testing.T, prune.Mode) error) {
err := runPermutation(t, testFunc, 0, prune.DefaultMode)
err := runPermutation(t, testFunc, 0, prune.MockMode)
if err != nil {
t.Errorf("error while testing stuff: %v", err)
}
Expand Down
10 changes: 5 additions & 5 deletions execution/stages/mock/mock_sentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,12 @@ func (ms *MockSentry) NodeInfo(context.Context, *emptypb.Empty) (*ptypes.NodeInf
const blockBufferSize = 128

func MockWithGenesis(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateKey, withPosDownloader bool) *MockSentry {
return MockWithGenesisPruneMode(tb, gspec, key, blockBufferSize, prune.DefaultMode, withPosDownloader)
return MockWithGenesisPruneMode(tb, gspec, key, blockBufferSize, prune.MockMode, withPosDownloader)
}

func MockWithGenesisEngine(tb testing.TB, gspec *types.Genesis, engine consensus.Engine, withPosDownloader, checkStateRoot bool) *MockSentry {
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
return MockWithEverything(tb, gspec, key, prune.DefaultMode, engine, blockBufferSize, false, withPosDownloader, checkStateRoot)
return MockWithEverything(tb, gspec, key, prune.MockMode, engine, blockBufferSize, false, withPosDownloader, checkStateRoot)
}

func MockWithGenesisPruneMode(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateKey, blockBufferSize int, prune prune.Mode, withPosDownloader bool) *MockSentry {
Expand Down Expand Up @@ -637,7 +637,7 @@ func MockWithTxPool(t *testing.T) *MockSentry {
}

checkStateRoot := true
return MockWithEverything(t, gspec, key, prune.DefaultMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
return MockWithEverything(t, gspec, key, prune.MockMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
}

func MockWithTxPoolCancun(t *testing.T) *MockSentry {
Expand All @@ -653,7 +653,7 @@ func MockWithTxPoolCancun(t *testing.T) *MockSentry {
}

checkStateRoot := true
return MockWithEverything(t, gspec, key, prune.DefaultMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
return MockWithEverything(t, gspec, key, prune.MockMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
}

func MockWithTxPoolOsaka(t *testing.T) *MockSentry {
Expand All @@ -670,7 +670,7 @@ func MockWithTxPoolOsaka(t *testing.T) *MockSentry {
}

checkStateRoot := true
return MockWithEverything(t, gspec, key, prune.DefaultMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
return MockWithEverything(t, gspec, key, prune.MockMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
}

func MockWithZeroTTD(t *testing.T, withPosDownloader bool) *MockSentry {
Expand Down
1 change: 1 addition & 0 deletions params/chainspecs/gnosis.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"londonBlock": 19040000,
"terminalTotalDifficulty": 8626000000000000000000058750000000000000000000,
"terminalTotalDifficultyPassed": true,
"mergeBlock": 25349537,
"shanghaiTime": 1690889660,
"cancunTime": 1710181820,
"pragueTime": 1746021820,
Expand Down
1 change: 1 addition & 0 deletions params/chainspecs/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"grayGlacierBlock": 15050000,
"terminalTotalDifficulty": 58750000000000000000000,
"terminalTotalDifficultyPassed": true,
"mergeBlock": 15537394,
"shanghaiTime": 1681338455,
"cancunTime": 1710338135,
"pragueTime": 1746612311,
Expand Down
1 change: 1 addition & 0 deletions params/chainspecs/sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"terminalTotalDifficulty": 17000000000000000,
"terminalTotalDifficultyPassed": true,
"mergeNetsplitBlock": 1735371,
"mergeBlock": 1450409,
"shanghaiTime": 1677557088,
"cancunTime": 1706655072,
"pragueTime": 1741159776,
Expand Down
1 change: 1 addition & 0 deletions turbo/cli/default_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var DefaultFlags = []cli.Flag{
&utils.TxPoolCommitEveryFlag,
&PruneDistanceFlag,
&PruneBlocksDistanceFlag,
&HistoryExpiryEnabledFlag,
&PruneModeFlag,
&utils.KeepExecutionProofsFlag,

Expand Down
28 changes: 22 additions & 6 deletions turbo/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/urfave/cli/v2"

"github.com/erigontech/erigon-lib/common"
"github.com/erigontech/erigon-lib/common/dbg"
"github.com/erigontech/erigon-lib/common/hexutil"
"github.com/erigontech/erigon-lib/etl"
"github.com/erigontech/erigon-lib/kv"
Expand Down Expand Up @@ -78,10 +79,11 @@ var (

PruneModeFlag = cli.StringFlag{
Name: "prune.mode",
Usage: `Choose a pruning preset to run onto. Available values: "full", "archive", "minimal".
Full: Keep only blocks and latest state,
Archive: Keep the entire indexed database, aka. no pruning,
Minimal: Keep only latest state`,
Usage: `Choose a pruning preset to run onto. Available values: "full", "archive", "minimal", "blocks".
full: Keep only necessary blocks and latest state,
blocks: Keep all blocks but not the state history,
archive: Keep the entire state history and all blocks,
minimal: Keep only latest state`,
Value: "full",
}
PruneDistanceFlag = cli.Uint64Flag{
Expand All @@ -92,7 +94,11 @@ var (
Name: "prune.distance.blocks",
Usage: `Keep block history for the latest N blocks (default: everything)`,
}

HistoryExpiryEnabledFlag = cli.BoolFlag{
Name: "history-expiry",
Usage: "Enable history expiry",
Value: true,
}
// mTLS flags
TLSFlag = cli.BoolFlag{
Name: "tls",
Expand Down Expand Up @@ -262,7 +268,10 @@ func ApplyFlagsForEthConfig(ctx *cli.Context, cfg *ethconfig.Config, logger log.
}
_ = chainId

mode, err := prune.FromCli(ctx.String(PruneModeFlag.Name), ctx.Uint64(PruneDistanceFlag.Name), ctx.Uint64(PruneBlocksDistanceFlag.Name))
blockDistance := ctx.Uint64(PruneBlocksDistanceFlag.Name)
distance := ctx.Uint64(PruneDistanceFlag.Name)

mode, err := prune.FromCli(ctx.String(PruneModeFlag.Name), distance, blockDistance)
if err != nil {
utils.Fatalf(fmt.Sprintf("error while parsing mode: %v", err))
}
Expand Down Expand Up @@ -369,6 +378,7 @@ func ApplyFlagsForEthConfigCobra(f *pflag.FlagSet, cfg *ethconfig.Config) {
if err != nil {
utils.Fatalf(fmt.Sprintf("error while parsing mode: %v", err))
}

cfg.Prune = mode

if v := f.String(BatchSizeFlag.Name, BatchSizeFlag.Value, BatchSizeFlag.Usage); v != nil {
Expand All @@ -377,6 +387,12 @@ func ApplyFlagsForEthConfigCobra(f *pflag.FlagSet, cfg *ethconfig.Config) {
utils.Fatalf("Invalid batchSize provided: %v", err)
}
}

enabledHistoryExpiry := f.Bool(HistoryExpiryEnabledFlag.Name, HistoryExpiryEnabledFlag.Value, HistoryExpiryEnabledFlag.Usage)
if enabledHistoryExpiry != nil && *enabledHistoryExpiry {
dbg.EnableHistoryExpiry = true
}

if v := f.String(EtlBufferSizeFlag.Name, EtlBufferSizeFlag.Value, EtlBufferSizeFlag.Usage); v != nil {
sizeVal := datasize.ByteSize(0)
size := &sizeVal
Expand Down
19 changes: 19 additions & 0 deletions turbo/snapshotsync/snapshotsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/erigontech/erigon-lib/chain"
"github.com/erigontech/erigon-lib/chain/snapcfg"
"github.com/erigontech/erigon-lib/common/datadir"
"github.com/erigontech/erigon-lib/common/dbg"
"github.com/erigontech/erigon-lib/config3"
proto_downloader "github.com/erigontech/erigon-lib/gointerfaces/downloaderproto"
"github.com/erigontech/erigon-lib/kv"
Expand Down Expand Up @@ -294,6 +295,21 @@ func computeBlocksToPrune(blockReader blockReader, p prune.Mode) (blocksToPrune
return p.Blocks.PruneTo(frozenBlocks), p.History.PruneTo(frozenBlocks)
}

// isTransactionsSegmentExpired - check if the transactions segment is expired according to whichever history expiry policy we use.
func isTransactionsSegmentExpired(cc *chain.Config, pruneMode prune.Mode, p snapcfg.PreverifiedItem) bool {
// History expiry is the default.
if pruneMode.Blocks != prune.DefaultBlocksPruneMode || !dbg.EnableHistoryExpiry {
return false
}

// We use the pre-merge data policy.
s, _, ok := snaptype.ParseFileName("", p.Name)
if !ok {
return false
}
return cc.IsPreMerge(s.From)
}

// WaitForDownloader - wait for Downloader service to download all expected snapshots
// for MVP we sync with Downloader only once, in future will send new snapshots also
func WaitForDownloader(
Expand Down Expand Up @@ -387,6 +403,9 @@ func WaitForDownloader(
if _, ok := blackListForPruning[p.Name]; ok {
continue
}
// if strings.Contains(p.Name, "transactions") && isTransactionsSegmentExpired(cc, prune, p) {
// continue
// }

downloadRequest = append(downloadRequest, DownloadRequest{
Path: p.Name,
Expand Down
Loading