Skip to content

Commit fda665c

Browse files
mh0ltAskAlexSharov
andauthored
execution/stagedsync, cmd/utils: --exec.no-prune now disables all DB pruning (#20915)
## Summary \`--exec.no-prune\` previously only gated the state-aggregator's pruning paths via \`dbg.NoPrune()\` (Domain/InvertedIndex in \`db/state/aggregator.go\`, forkable in \`db/state/forkable_agg.go\`). Stage-level pruning ran regardless: - \`PruneExecutionStage\` was calling \`rawdb.PruneTable\` on \`kv.ChangeSets3\` and \`kv.BlockAccessList\`. - \`PruneTxLookup\`, \`PruneWitnessProcessingStage\` and \`SnapshotsPrune\` (\`PruneAncientBlocks\`, \`pruneCanonicalMarkers\`, \`RetireBlocksInBackground\`, \`pruneBlockSnapshots\`) had no guard. ## Fix Add early-return guards on \`dbg.NoPrune()\` at the top of each stage prune entrypoint so the flag genuinely blocks every path that removes rows from MDBX. Each function still calls \`s.Done(tx)\` so the staged-sync state machine doesn't re-enter the prune step on every cycle. Updated flag usage text to describe the wider semantic. Companion bal-devnet-3 PR: #20914 ## Test plan - [x] \`go test ./execution/stagedsync -run TestNoPrune\` — new \`no_prune_test.go\` seeds rows in \`kv.ChangeSets3\`, \`kv.BlockAccessList\`, \`kv.TxLookup\`, \`kv.BorWitnesses\`; runs all four prune entrypoints with \`NoPrune=true\`; asserts no rows deleted and \`PruneProgress\` is recorded. - [x] \`make lint\` — clean. - [x] \`make erigon\` — builds. Co-authored-by: Alex Sharov <AskAlexSharov@gmail.com>
1 parent c8a8ad3 commit fda665c

6 files changed

Lines changed: 135 additions & 1 deletion

File tree

cmd/utils/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@ var (
11771177
}
11781178
ExecNoPruneFlag = cli.BoolFlag{
11791179
Name: "exec.no-prune",
1180-
Usage: "Disable state-aggregator pruning of historical steps (equivalent to NO_PRUNE=true). Diagnostic / perf-comparison use only.",
1180+
Usage: "Disable all DB pruning: state-aggregator (Domain/InvertedIndex/forkable) plus stage-level pruning (Execution: ChangeSets3/BlockAccessList; TxLookup; WitnessProcessing; Snapshots: PruneAncientBlocks/canonical markers/retirement) (equivalent to NO_PRUNE=true). Diagnostic / perf-comparison use only.",
11811181
Value: false,
11821182
}
11831183
)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2026 The Erigon Authors
2+
// This file is part of Erigon.
3+
//
4+
// Erigon is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Erigon is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package stagedsync
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/erigontech/erigon/common/dbg"
26+
"github.com/erigontech/erigon/common/log/v3"
27+
"github.com/erigontech/erigon/db/kv"
28+
"github.com/erigontech/erigon/db/kv/dbcfg"
29+
"github.com/erigontech/erigon/db/kv/memdb"
30+
"github.com/erigontech/erigon/execution/stagedsync/stages"
31+
)
32+
33+
// TestNoPruneSkipsAllPruneStages verifies that when --exec.no-prune is set
34+
// (dbg.NoPrune() == true), each staged-sync prune entrypoint is a no-op
35+
// against the MDBX tables it would otherwise delete rows from.
36+
func TestNoPruneSkipsAllPruneStages(t *testing.T) {
37+
orig := dbg.NoPrune()
38+
t.Cleanup(func() { dbg.SetNoPrune(orig) })
39+
dbg.SetNoPrune(true)
40+
41+
ctx := context.Background()
42+
logger := log.New()
43+
44+
db := memdb.NewTestDB(t, dbcfg.ChainDB)
45+
tx, err := db.BeginRw(ctx)
46+
require.NoError(t, err)
47+
defer tx.Rollback()
48+
49+
// Seed rows in every table any of the prune stages would target.
50+
type seedRow struct{ table, key, value string }
51+
seeds := []seedRow{
52+
{kv.ChangeSets3, "k1", "v1"},
53+
{kv.ChangeSets3, "k2", "v2"},
54+
{kv.BlockAccessList, "b1", "ba1"},
55+
{kv.BlockAccessList, "b2", "ba2"},
56+
{kv.TxLookup, "t1", "tl1"},
57+
{kv.BorWitnesses, "w1", "wit1"},
58+
}
59+
for _, s := range seeds {
60+
require.NoError(t, tx.Put(s.table, []byte(s.key), []byte(s.value)))
61+
}
62+
countRows := func(t *testing.T, table string) int {
63+
c, err := tx.Cursor(table)
64+
require.NoError(t, err)
65+
defer c.Close()
66+
n := 0
67+
for k, _, err := c.First(); k != nil; k, _, err = c.Next() {
68+
require.NoError(t, err)
69+
n++
70+
}
71+
return n
72+
}
73+
tracked := []string{kv.ChangeSets3, kv.BlockAccessList, kv.TxLookup, kv.BorWitnesses}
74+
pre := map[string]int{}
75+
for _, table := range tracked {
76+
pre[table] = countRows(t, table)
77+
require.Greater(t, pre[table], 0, "expected seeded rows in %s", table)
78+
}
79+
80+
// ForwardProgress is well past MaxReorgDepth so the inner rawdb.PruneTable /
81+
// PruneSmallBatches calls would normally fire. Each prune function
82+
// early-returns on dbg.NoPrune() before reading any cfg field, so zero-value
83+
// cfgs are safe.
84+
const forward uint64 = 10_000
85+
require.NoError(t, PruneExecutionStage(ctx, &PruneState{ID: stages.Execution, ForwardProgress: forward}, tx, ExecuteBlockCfg{}, 0, logger))
86+
require.NoError(t, PruneTxLookup(&PruneState{ID: stages.TxLookup, ForwardProgress: forward}, tx, TxLookupCfg{}, ctx, logger))
87+
require.NoError(t, PruneWitnessProcessingStage(&PruneState{ID: stages.WitnessProcessing, ForwardProgress: forward}, tx, WitnessProcessingCfg{}, ctx, logger))
88+
require.NoError(t, SnapshotsPrune(&PruneState{ID: stages.Snapshots, ForwardProgress: forward}, SnapshotsCfg{}, ctx, tx, logger))
89+
90+
for _, table := range tracked {
91+
require.Equal(t, pre[table], countRows(t, table), "table %s lost rows under --exec.no-prune", table)
92+
}
93+
}
94+
95+
// TestNoPruneFlagBookkeeping confirms each prune stage still records its
96+
// PruneProgress when skipping work — otherwise the staged-sync state machine
97+
// would re-enter the prune step on every cycle.
98+
func TestNoPruneFlagBookkeeping(t *testing.T) {
99+
orig := dbg.NoPrune()
100+
t.Cleanup(func() { dbg.SetNoPrune(orig) })
101+
dbg.SetNoPrune(true)
102+
103+
ctx := context.Background()
104+
logger := log.New()
105+
106+
db := memdb.NewTestDB(t, dbcfg.ChainDB)
107+
tx, err := db.BeginRw(ctx)
108+
require.NoError(t, err)
109+
defer tx.Rollback()
110+
111+
const forward uint64 = 12_345
112+
require.NoError(t, PruneExecutionStage(ctx, &PruneState{ID: stages.Execution, ForwardProgress: forward}, tx, ExecuteBlockCfg{}, 0, logger))
113+
require.NoError(t, PruneTxLookup(&PruneState{ID: stages.TxLookup, ForwardProgress: forward}, tx, TxLookupCfg{}, ctx, logger))
114+
require.NoError(t, PruneWitnessProcessingStage(&PruneState{ID: stages.WitnessProcessing, ForwardProgress: forward}, tx, WitnessProcessingCfg{}, ctx, logger))
115+
116+
for _, id := range []stages.SyncStage{stages.Execution, stages.TxLookup, stages.WitnessProcessing} {
117+
got, err := stages.GetStagePruneProgress(tx, id)
118+
require.NoError(t, err)
119+
require.Equal(t, forward, got, "stage %s did not record PruneProgress under --exec.no-prune", id)
120+
}
121+
}

execution/stagedsync/stage_execute.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ func UnwindExecutionStage(u *UnwindState, s *StageState, doms *execctx.SharedDom
454454
}
455455

456456
func PruneExecutionStage(ctx context.Context, s *PruneState, tx kv.RwTx, cfg ExecuteBlockCfg, timeout time.Duration, logger log.Logger) (err error) {
457+
if dbg.NoPrune() {
458+
return s.Done(tx)
459+
}
457460
// on chain-tip:
458461
// - can prune only between blocks (without blocking blocks processing)
459462
// - need also leave some time to prune blocks

execution/stagedsync/stage_snapshots.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,9 @@ func pruneCanonicalMarkers(ctx context.Context, tx kv.RwTx, blockReader services
427427

428428
// SnapshotsPrune moving block data from db into snapshots, removing old snapshots (if --prune.* enabled)
429429
func SnapshotsPrune(s *PruneState, cfg SnapshotsCfg, ctx context.Context, tx kv.RwTx, logger log.Logger) (err error) {
430+
if dbg.NoPrune() {
431+
return nil
432+
}
430433
freezingCfg := cfg.blockReader.FreezingCfg()
431434
if freezingCfg.ProduceE2 {
432435
//TODO: initialSync maybe save files progress here

execution/stagedsync/stage_txlookup.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ func UnwindTxLookup(u *UnwindState, s *StageState, tx kv.RwTx, cfg TxLookupCfg,
201201
}
202202

203203
func PruneTxLookup(s *PruneState, tx kv.RwTx, cfg TxLookupCfg, ctx context.Context, logger log.Logger) (err error) {
204+
if dbg.NoPrune() {
205+
return s.Done(tx)
206+
}
204207
logPrefix := s.LogPrefix()
205208
blockFrom := s.PruneProgress
206209
if blockFrom == 0 {

execution/stagedsync/stage_witness_processing.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sync"
2424

2525
"github.com/erigontech/erigon/common"
26+
"github.com/erigontech/erigon/common/dbg"
2627
"github.com/erigontech/erigon/common/hexutil"
2728
"github.com/erigontech/erigon/common/log/v3"
2829
"github.com/erigontech/erigon/db/kv"
@@ -152,6 +153,9 @@ func UnwindWitnessProcessingStage(u *UnwindState, s *StageState, tx kv.RwTx, ctx
152153

153154
// PruneWitnessProcessingStage handles pruning for witness processing
154155
func PruneWitnessProcessingStage(p *PruneState, tx kv.RwTx, cfg WitnessProcessingCfg, ctx context.Context, logger log.Logger) error {
156+
if dbg.NoPrune() {
157+
return p.Done(tx)
158+
}
155159
// Prune old witness data based on retention policy
156160
if err := cleanupOldWitnesses(tx, p.ForwardProgress, logger); err != nil {
157161
return err

0 commit comments

Comments
 (0)