Skip to content

Commit 2fad07d

Browse files
wmitsudaclaude
andauthored
db/state, snapcfg: download erigondb.toml via torrent; move settings resolution out of Open() (#19587)
## Summary - Move `ResolveErigonDBSettings` out of `Aggregator.Open()` so callers resolve settings explicitly before constructing the aggregator, enabling the downloader to provide the real `erigondb.toml` before domains are configured. - Whitelist `erigondb.toml` in the header-chain download phase so the torrent downloader delivers it alongside headers/bodies. - After the header-chain download completes, `ReloadErigonDBSettings()` re-reads the file and propagates any stepSize changes to all Domain/InvertedIndex instances. - Add `WithErigonDBSettings()` builder on `AggOpts` so all call sites pass pre-resolved settings. Three runtime scenarios handled: 1. **Legacy datadir** (has `preverified.toml`, no `erigondb.toml`): writes `erigondb.toml` with legacy settings (step_size=1,562,500) on startup. 2. **Fresh sync with downloader**: starts with defaults, downloader delivers real `erigondb.toml` during header-chain phase, settings are reloaded and propagated. 3. **Fresh sync with `--no-downloader`**: writes defaults to disk immediately since no downloader will provide the file. ## Test plan - [x] Scenario 1 (legacy datadir): confirmed log `Creating erigondb.toml with LEGACY settings step_size=1562500` and file written on startup - [x] Scenario 2 (fresh + downloader): confirmed log `erigondb stepSize changed, propagating` after header-chain download delivers `erigondb.toml` with step_size=390625 - [x] Scenario 3 (fresh + `--no-downloader`): confirmed log `Initializing erigondb.toml with DEFAULT settings (nodownloader)` and file written immediately --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8d4b28d commit 2fad07d

File tree

19 files changed

+350
-102
lines changed

19 files changed

+350
-102
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
name: erigondb-sync-integration-test-plan
3+
description: Integration test plan for erigondb.toml settings resolution across 3 runtime scenarios (legacy, fresh+downloader, fresh+no-downloader)
4+
allowed-tools: Bash, Read
5+
user-invocable: false
6+
---
7+
8+
# erigondb.toml Integration Test Plan
9+
10+
Verify that `erigondb.toml` settings are correctly resolved across three distinct runtime scenarios: legacy datadirs, fresh sync with downloader, and fresh sync without downloader.
11+
12+
Use this plan after any changes to the erigondb.toml resolution logic (creation, defaults, legacy detection, downloader delivery) to confirm all paths still work.
13+
14+
## Parameters
15+
16+
Two values must be provided by the caller:
17+
18+
| Parameter | Description | Example |
19+
|-----------|-------------|---------|
20+
| **chain** | Chain name passed to `--chain=` | `hoodi`, `mainnet` |
21+
| **legacy datadir** | Path to an existing Erigon datadir that has `preverified.toml` but **no** `erigondb.toml` | `~/eth-nodes/erigon33-hoodi-stable` |
22+
23+
All scenarios use `--prune.mode=archive`.
24+
25+
## Pre-requisites
26+
27+
1. **Built binary** — invoke `/erigon-build` if `./build/bin/erigon` does not exist.
28+
2. **Legacy datadir** — the user-provided path must contain `snapshots/preverified.toml` and must **not** contain `snapshots/erigondb.toml`.
29+
3. **Port availability** — use `/erigon-network-ports` for reference. Each scenario uses a different port offset (+100, +200, +300) via `/erigon-ephemeral` conflict detection.
30+
31+
## Scenario A: Legacy datadir
32+
33+
**Goal**: Starting on a legacy datadir (has `preverified.toml`, no `erigondb.toml`) creates `erigondb.toml` with legacy settings (`step_size = 1562500`).
34+
35+
### Steps
36+
37+
1. Create an ephemeral clone of the user's legacy datadir using `/erigon-ephemeral` (Mode B: clone). This gives an isolated copy so the original is untouched.
38+
2. Confirm pre-conditions on the clone:
39+
- `snapshots/preverified.toml` exists
40+
- `snapshots/erigondb.toml` does **not** exist
41+
3. Start erigon with port offset **+100** (via `/erigon-ephemeral` port conflict detection):
42+
```
43+
./build/bin/erigon \
44+
--datadir=<clone-path> \
45+
--chain=<chain> \
46+
--prune.mode=archive \
47+
<port flags at +100 offset>
48+
```
49+
4. Wait for startup (~15s). Check logs for a message indicating legacy settings detection (e.g., `Creating erigondb.toml with LEGACY settings`).
50+
5. Verify the generated file:
51+
```bash
52+
cat <clone-path>/snapshots/erigondb.toml
53+
```
54+
Expected: `step_size = 1562500`
55+
6. Kill the process and clean up the ephemeral datadir.
56+
57+
## Scenario B: Fresh sync with downloader
58+
59+
**Goal**: A fresh datadir (no `preverified.toml`, no `erigondb.toml`) starts with code defaults (`step_size = 1562500`), then the downloader delivers the network's `erigondb.toml` during the header-chain phase, which may have different settings (e.g., `step_size = 390625` for hoodi).
60+
61+
### Steps
62+
63+
1. Create an empty ephemeral datadir using `/erigon-ephemeral` (Mode A: empty).
64+
2. Start erigon with port offset **+200**:
65+
```
66+
./build/bin/erigon \
67+
--datadir=<empty-path> \
68+
--chain=<chain> \
69+
--prune.mode=archive \
70+
<port flags at +200 offset>
71+
```
72+
3. Check early logs for a message indicating defaults are being used (e.g., `erigondb.toml not found, using defaults`) with `step_size=1562500`.
73+
4. Wait for the header-chain download phase to complete (~30-120s depending on network). Watch logs for:
74+
- `[1/6 OtterSync] Downloader completed header-chain`
75+
- `Reading DB settings from existing erigondb.toml`
76+
- `erigondb stepSize changed, propagating` (if the network's settings differ from the code defaults)
77+
5. Verify the delivered file:
78+
```bash
79+
cat <empty-path>/snapshots/erigondb.toml
80+
```
81+
The values come from the network's published `erigondb.toml` (check the chain's webseed for the canonical values).
82+
6. Kill the process and clean up the ephemeral datadir.
83+
84+
## Scenario C: Fresh sync with `--no-downloader`
85+
86+
**Goal**: A fresh datadir with `--no-downloader` immediately writes `erigondb.toml` with code defaults (`step_size = 1562500`).
87+
88+
### Steps
89+
90+
1. Create an empty ephemeral datadir using `/erigon-ephemeral` (Mode A: empty).
91+
2. Start erigon with port offset **+300** and `--no-downloader`:
92+
```
93+
./build/bin/erigon \
94+
--datadir=<empty-path> \
95+
--chain=<chain> \
96+
--prune.mode=archive \
97+
--no-downloader \
98+
<port flags at +300 offset>
99+
```
100+
3. Check logs for a message indicating defaults were written immediately (e.g., `Initializing erigondb.toml with DEFAULT settings`) with `step_size=1562500`.
101+
4. Verify the file exists immediately:
102+
```bash
103+
cat <empty-path>/snapshots/erigondb.toml
104+
```
105+
Expected: `step_size = 1562500` (code defaults, since no downloader to provide network settings)
106+
5. Kill the process and clean up the ephemeral datadir.
107+
108+
## Success Criteria
109+
110+
| Scenario | Condition | Expected step_size | File timing |
111+
|----------|-----------|-------------------|-------------|
112+
| A (legacy) | `Creating erigondb.toml with LEGACY settings` log | 1,562,500 | Written immediately on startup |
113+
| B (fresh+downloader) | `erigondb.toml not found, using defaults` then `erigondb stepSize changed, propagating` | Code default 1,562,500 then network value (chain-dependent) | After downloader delivers it |
114+
| C (fresh+no-downloader) | `Initializing erigondb.toml with DEFAULT settings (nodownloader)` log | 1,562,500 | Written immediately on startup |
115+
116+
## Cleanup
117+
118+
After all scenarios complete, ensure all ephemeral datadirs and processes are cleaned up. Use `/erigon-ephemeral` Step 5 (cleanup) for each instance, or Step 6 (leftover detection) to find any stragglers.

cmd/integration/commands/stages.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,11 @@ func allSnapshots(ctx context.Context, db kv.RoDB, logger log.Logger) (*freezebl
11331133
blockReader := freezeblocks.NewBlockReader(_allSnapshotsSingleton, _allBorSnapshotsSingleton)
11341134
txNums := blockReader.TxnumReader()
11351135

1136-
_aggSingleton = dbstate.New(dirs).Logger(logger).MustOpen(ctx, db)
1136+
var erigonDBSettings *dbstate.ErigonDBSettings
1137+
if erigonDBSettings, err = dbstate.ResolveErigonDBSettings(dirs, logger, false); err != nil {
1138+
return
1139+
}
1140+
_aggSingleton = dbstate.New(dirs).Logger(logger).WithErigonDBSettings(erigonDBSettings).MustOpen(ctx, db)
11371141

11381142
_aggSingleton.SetProduceMod(snapCfg.ProduceE3)
11391143

cmd/rpcdaemon/cli/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ func RemoteServices(ctx context.Context, cfg *httpcfg.HttpCfg, logger log.Logger
439439
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err
440440
}
441441

442-
agg, err := dbstate.New(cfg.Dirs).Logger(logger).Open(ctx, rawDB)
442+
erigonDBSettings, err := dbstate.ResolveErigonDBSettings(cfg.Dirs, logger, false)
443+
if err != nil {
444+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err
445+
}
446+
agg, err := dbstate.New(cfg.Dirs).Logger(logger).WithErigonDBSettings(erigonDBSettings).Open(ctx, rawDB)
443447
if err != nil {
444448
return nil, nil, nil, nil, nil, nil, nil, ff, nil, nil, fmt.Errorf("create aggregator: %w", err)
445449
}

cmd/state/commands/opcode_tracer.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,11 @@ func OpcodeTracer(genesis *types.Genesis, blockNum uint64, chaindata string, num
436436
rawChainDb := mdbx.MustOpen(dirs.Chaindata)
437437
defer rawChainDb.Close()
438438

439-
agg, err := dbstate.New(dirs).Logger(logger).Open(context.Background(), rawChainDb)
439+
erigonDBSettings, err := dbstate.ResolveErigonDBSettings(dirs, logger, false)
440+
if err != nil {
441+
return err
442+
}
443+
agg, err := dbstate.New(dirs).Logger(logger).WithErigonDBSettings(erigonDBSettings).Open(context.Background(), rawChainDb)
440444
if err != nil {
441445
return err
442446
}

cmd/utils/app/snapshots_cmd.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2898,7 +2898,11 @@ func dbCfg(label kv.Label, path string) mdbx.MdbxOpts {
28982898
Accede(true) // integration tool: open db without creation and without blocking erigon
28992899
}
29002900
func openAgg(ctx context.Context, dirs datadir.Dirs, chainDB kv.RwDB, logger log.Logger) *state.Aggregator {
2901-
agg, err := state.New(dirs).SanityOldNaming().Logger(logger).Open(ctx, chainDB)
2901+
erigonDBSettings, err := state.ResolveErigonDBSettings(dirs, logger, false)
2902+
if err != nil {
2903+
panic(err)
2904+
}
2905+
agg, err := state.New(dirs).SanityOldNaming().Logger(logger).WithErigonDBSettings(erigonDBSettings).Open(ctx, chainDB)
29022906
if err != nil {
29032907
panic(err)
29042908
}

cmd/utils/app/squeeze_cmd.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ func squeezeStorage(ctx context.Context, dirs datadir.Dirs, logger log.Logger) e
141141
ac := agg.BeginFilesRo()
142142
defer ac.Close()
143143

144-
aggOld, err := state.New(dirsOld).Logger(logger).Open(ctx, db)
144+
erigonDBSettingsOld, err := state.ResolveErigonDBSettings(dirsOld, logger, false)
145+
if err != nil {
146+
panic(err)
147+
}
148+
aggOld, err := state.New(dirsOld).Logger(logger).WithErigonDBSettings(erigonDBSettingsOld).Open(ctx, db)
145149
if err != nil {
146150
panic(err)
147151
}
@@ -183,7 +187,11 @@ func squeezeStorage(ctx context.Context, dirs datadir.Dirs, logger log.Logger) e
183187
func squeezeCode(ctx context.Context, dirs datadir.Dirs, logger log.Logger) error {
184188
db := dbCfg(dbcfg.ChainDB, dirs.Chaindata).MustOpen()
185189
defer db.Close()
186-
agg := state.New(dirs).Logger(logger).MustOpen(ctx, db)
190+
erigonDBSettings, err := state.ResolveErigonDBSettings(dirs, logger, false)
191+
if err != nil {
192+
return err
193+
}
194+
agg := state.New(dirs).Logger(logger).WithErigonDBSettings(erigonDBSettings).MustOpen(ctx, db)
187195
defer agg.Close()
188196
agg.SetCompressWorkers(estimate.CompressSnapshot.Workers())
189197

cmd/utils/app/step_cmd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func stepRebase(cliCtx *cli.Context) error {
2727
defer ticker.Stop()
2828

2929
dirs := datadir.Open(cliCtx.String("datadir"))
30-
settings, err := state.CreateOrReadErigonDBSettings(dirs, logger)
30+
settings, err := state.ResolveErigonDBSettings(dirs, logger, true)
3131
if err != nil {
3232
return err
3333
}

db/snapcfg/util.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ func (p Preverified) Typed(types []snaptype.Type) Preverified {
154154
bestVersions.Set(p.Name, p)
155155
continue
156156
}
157+
if p.Name == "erigondb.toml" {
158+
bestVersions.Set(p.Name, p)
159+
continue
160+
}
157161

158162
v, name, ok := strings.Cut(p.Name, "-")
159163
if !ok {

db/snapshotsync/snapshotsync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ func SyncSnapshots(
412412
continue
413413
}
414414
if headerchain &&
415-
!(strings.Contains(p.Name, "headers") || strings.Contains(p.Name, "bodies") || p.Name == "salt-blocks.txt") {
415+
!(strings.Contains(p.Name, "headers") || strings.Contains(p.Name, "bodies") || p.Name == "salt-blocks.txt" || p.Name == "erigondb.toml") {
416416
continue
417417
}
418418
if !syncCfg.KeepExecutionProofs && isStateHistory(p.Name) && strings.Contains(p.Name, kv.CommitmentDomain.String()) {

db/state/aggregator.go

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ type Aggregator struct {
9898
produce bool
9999

100100
checker *DependencyIntegrityChecker
101+
102+
// Domain configuration state: ConfigureDomains() is a no-op once configured is true.
103+
configured bool
104+
savedSalt *uint32
105+
disableFsync bool
101106
}
102107

103108
func newAggregator(ctx context.Context, dirs datadir.Dirs, reorgBlockDepth uint64, db kv.RoDB, logger log.Logger) (*Aggregator, error) {
@@ -234,23 +239,90 @@ func (a *Aggregator) reloadSalt() error {
234239
}
235240

236241
for _, d := range a.d {
237-
d.salt.Store(salt)
242+
if d != nil {
243+
d.salt.Store(salt)
244+
}
238245
}
239246

240247
for _, ii := range a.iis {
241-
ii.salt.Store(salt)
248+
if ii != nil {
249+
ii.salt.Store(salt)
250+
}
242251
}
243252

244253
return nil
245254
}
246255

247-
func (a *Aggregator) reloadErigonDBSettings() error {
248-
settings, err := CreateOrReadErigonDBSettings(a.dirs, a.logger)
256+
// ReloadErigonDBSettings re-reads erigondb.toml from disk and updates the in-memory stepSize.
257+
// If domains are already configured and stepSize changed, it propagates the new value to all
258+
// Domain/InvertedIndex instances.
259+
func (a *Aggregator) ReloadErigonDBSettings(noDownloader bool) error {
260+
oldStepSize := a.stepSize
261+
oldStepsInFrozenFile := a.stepsInFrozenFile
262+
263+
settings, err := ResolveErigonDBSettings(a.dirs, a.logger, noDownloader)
249264
if err != nil {
250265
return err
251266
}
252267
a.stepSize = settings.StepSize
253268
a.stepsInFrozenFile = settings.StepsInFrozenFile
269+
270+
if a.configured && (a.stepSize != oldStepSize || a.stepsInFrozenFile != oldStepsInFrozenFile) {
271+
a.logger.Info("erigondb stepSize changed, propagating to domains/IIs",
272+
"old_step_size", oldStepSize, "new_step_size", a.stepSize,
273+
"old_steps_in_frozen_file", oldStepsInFrozenFile, "new_steps_in_frozen_file", a.stepsInFrozenFile)
274+
// stepSize lives only on InvertedIndex; Domain and History access it through embedding.
275+
// Update the InvertedIndex embedded in each domain's History, plus standalone IIs.
276+
for _, d := range a.d {
277+
if d != nil && d.History != nil && d.History.InvertedIndex != nil {
278+
d.History.InvertedIndex.setStepSize(a.stepSize, a.stepsInFrozenFile)
279+
}
280+
}
281+
for _, ii := range a.iis {
282+
if ii != nil {
283+
ii.setStepSize(a.stepSize, a.stepsInFrozenFile)
284+
}
285+
}
286+
}
287+
return nil
288+
}
289+
290+
// ConfigureDomains creates all Domain and InvertedIndex instances. This is a no-op if domains
291+
// are already configured. It requires stepSize > 0 (which NewInvertedIndex enforces via panic).
292+
func (a *Aggregator) ConfigureDomains() error {
293+
if a.configured {
294+
return nil
295+
}
296+
if a.stepSize == 0 {
297+
return fmt.Errorf("cannot configure domains: stepSize is 0")
298+
}
299+
// AdjustReceipt mutates the global statecfg.Schema; must run before Configure().
300+
if err := statecfg.AdjustReceiptCurrentVersionIfNeeded(a.dirs, a.logger); err != nil {
301+
return err
302+
}
303+
if err := statecfg.Configure(statecfg.Schema, a, a.dirs, a.savedSalt, a.logger); err != nil {
304+
return err
305+
}
306+
a.configured = true
307+
308+
if a.disableFsync {
309+
for _, d := range a.d {
310+
if d != nil {
311+
d.DisableFsync()
312+
}
313+
}
314+
for _, ii := range a.iis {
315+
if ii != nil {
316+
ii.DisableFsync()
317+
}
318+
}
319+
}
320+
321+
func() {
322+
a.dirtyFilesLock.Lock()
323+
defer a.dirtyFilesLock.Unlock()
324+
a.recalcVisibleFiles(a.dirtyFilesEndTxNumMinimax())
325+
}()
254326
return nil
255327
}
256328

@@ -430,13 +502,19 @@ func (a *Aggregator) Close() {
430502
func (a *Aggregator) closeDirtyFiles() {
431503
wg := &sync.WaitGroup{}
432504
for _, d := range a.d {
505+
if d == nil {
506+
continue
507+
}
433508
wg.Add(1)
434509
go func() {
435510
defer wg.Done()
436511
d.Close()
437512
}()
438513
}
439514
for _, ii := range a.iis {
515+
if ii == nil {
516+
continue
517+
}
440518
wg.Add(1)
441519
go func() {
442520
defer wg.Done()
@@ -464,6 +542,9 @@ func (a *Aggregator) SetCompressWorkers(i int) {
464542
return
465543
}
466544
for _, d := range a.d {
545+
if d == nil {
546+
continue
547+
}
467548
d.CompressCfg.Workers = i
468549
if d.History != nil {
469550
d.History.CompressorCfg.Workers = i
@@ -1332,6 +1413,9 @@ func (a *Aggregator) FirstTxNumOfStep(step kv.Step) uint64 { // could have some
13321413
}
13331414

13341415
func (a *Aggregator) dirtyFilesEndTxNumMinimax() uint64 {
1416+
if a.d[kv.AccountsDomain] == nil || a.d[kv.StorageDomain] == nil || a.d[kv.CodeDomain] == nil {
1417+
return 0
1418+
}
13351419
m := min(
13361420
a.d[kv.AccountsDomain].dirtyFilesEndTxNumMinimax(),
13371421
a.d[kv.StorageDomain].dirtyFilesEndTxNumMinimax(),

0 commit comments

Comments
 (0)