Summary
Prove the physical distribution flow for decentralized snapshot info-hash distribution using discv5 ENR metadata and BitTorrent.
Background
Currently, snapshot info-hashes are distributed via a centralized path: the erigon-snapshot GitHub repo embeds hashes into the binary as preverified.toml, with a runtime fallback to R2/GitHub. This creates a single point of failure and a central trust dependency.
This POC adds a parallel P2P distribution path alongside the existing system:
- Publish
chain.toml (new file, parallel to preverified.toml) as a BitTorrent file
- Advertise its info-hash via discv5 ENR entries
- New nodes discover peers, read ENR entries, and download
chain.toml via BitTorrent
Non-destructive rollout: The existing preverified.toml system remains completely untouched. chain.toml is a new parallel file. All existing download logic stays as-is. The new system runs alongside and can be removed without impact.
File Lifecycle
Files now have a 3-stage lifecycle:
- Locally generated — node creates .seg files, builds .torrent, publishes to BitTorrent
- In local chain.toml — node's manifest lists info-hashes as its known-good set
- Confirmed good — multiple peers agree on same info-hashes (what embedded preverified represents today)
A new chain.toml is produced for every max step, driven by the max frozen transaction number (immutable increasing counter).
Design
ENR Entry
A custom ENR entry carries the frozen tx number (monotonic version) and the torrent info-hash:
type ChainToml struct {
FrozenTx uint64 // max frozen transaction number
InfoHash [20]byte // torrent info-hash of chain.toml
}
func (v ChainToml) ENRKey() string { return "chain-toml" }
28 bytes total — well within the 300-byte ENR limit.
Deterministic Info-Hash
chain.toml is generated deterministically from the node's current .torrent files (name → infohash map). Same snapshot set = same file content = same torrent info-hash.
Publish Flow
- Generate
chain.toml from local .torrent files
- Create torrent of
chain.toml (fixed piece length)
- Set ENR entry:
{FrozenTx: <max_frozen_tx>, InfoHash: <chain.toml torrent hash>}
- Seed the torrent
Discovery Flow
- Discover peers via discv5
- Read
"chain-toml" ENR entry from peers
- Find peer(s) with highest
FrozenTx
- Download
chain.toml via BitTorrent using the info-hash
- For POC: chain.toml is informational (existing preverified.toml flow handles actual downloads)
Update Propagation
When snapshots advance to a new max step:
- New
chain.toml generated (superset of previous)
- New torrent → new info-hash
- ENR updated with new FrozenTx + InfoHash
- Peers discover updated ENR during normal discv5 refresh
Implementation Plan
Phase 1: ENR Entry Type
New file: p2p/enr/chain_toml.go + tests
Define ChainToml ENR entry with RLP encode/decode following existing patterns in p2p/enr/entries.go.
Phase 2: chain.toml Generation (Parallel to preverified.toml)
New file: db/downloader/chaintoml.go
No existing files modified. Core functions:
GenerateChainToml(snapDir) — scan .torrent files, produce TOML bytes
SaveChainToml(snapDir, tomlBytes) — atomic write
LoadChainToml(snapDir) — read local file
BuildChainTomlTorrent(snapDir, torrentFS) — build .torrent, return info-hash
PublishChainToml(snapDir, torrentFS, enrUpdater) — orchestrate generate → save → torrent → ENR
Phase 3: Torrent Creation + ENR Advertisement
Add enrUpdater callback to Downloader struct. Wire in node/eth/backend.go.
Trigger points (additive hooks after existing logic):
- After
SaveSnapshotHashes in stage_snapshots.go
- After
seeder.Seed() in MergeBlocks onMerge callback
Phase 4: P2P Discovery + Download
New file: db/downloader/p2p_chaintoml.go
DiscoverChainToml(discv5) — iterate peers, find highest FrozenTx
DownloadChainToml(torrentClient, infoHash) — download chain.toml via BitTorrent
Runs as a separate path from LoadSnapshotsHashes — no modification to existing startup.
Phase 5: Background Update Loop
Goroutine in Downloader, gated by feature flag. Every 5 minutes: discover peers, download newer chain.toml if available, validate append-only property, update local file + ENR.
Phase 6: Graceful Removal
Entire feature removable by: deleting chain.toml + its .torrent, restarting without feature flag, deleting new source files. No existing code affected.
Files
New files
| File |
Purpose |
p2p/enr/chain_toml.go |
ChainToml ENR entry type + RLP |
p2p/enr/chain_toml_test.go |
ENR roundtrip tests |
db/downloader/chaintoml.go |
Generation, saving, torrent creation, ENR update, background loop |
db/downloader/chaintoml_test.go |
Unit tests |
db/downloader/p2p_chaintoml.go |
P2P discovery + download logic |
Modified files (additive only)
| File |
Change |
node/eth/backend.go |
Wire ENR updater callback |
execution/stagedsync/stage_snapshots.go |
Add PublishChainToml after SaveSnapshotHashes |
db/snapshotsync/freezeblocks/block_snapshots.go |
Add PublishChainToml in onMerge |
db/downloader/downloader.go |
Add enrUpdater field + background loop |
Untouched (explicitly)
db/datadir/dirs.go — PreverifiedFileName stays as "preverified.toml"
db/downloader/downloadercfg/downloadercfg.go — LoadSnapshotsHashes / SaveSnapshotHashes unchanged
db/snapcfg/util.go — Cfg.Local flag unchanged
db/snapshotsync/snapshotsync.go — SyncSnapshots Local check unchanged
Acceptance Criteria
Summary
Prove the physical distribution flow for decentralized snapshot info-hash distribution using discv5 ENR metadata and BitTorrent.
Background
Currently, snapshot info-hashes are distributed via a centralized path: the
erigon-snapshotGitHub repo embeds hashes into the binary aspreverified.toml, with a runtime fallback to R2/GitHub. This creates a single point of failure and a central trust dependency.This POC adds a parallel P2P distribution path alongside the existing system:
chain.toml(new file, parallel topreverified.toml) as a BitTorrent filechain.tomlvia BitTorrentNon-destructive rollout: The existing
preverified.tomlsystem remains completely untouched.chain.tomlis a new parallel file. All existing download logic stays as-is. The new system runs alongside and can be removed without impact.File Lifecycle
Files now have a 3-stage lifecycle:
A new
chain.tomlis produced for every max step, driven by the max frozen transaction number (immutable increasing counter).Design
ENR Entry
A custom ENR entry carries the frozen tx number (monotonic version) and the torrent info-hash:
28 bytes total — well within the 300-byte ENR limit.
Deterministic Info-Hash
chain.tomlis generated deterministically from the node's current.torrentfiles (name → infohash map). Same snapshot set = same file content = same torrent info-hash.Publish Flow
chain.tomlfrom local.torrentfileschain.toml(fixed piece length){FrozenTx: <max_frozen_tx>, InfoHash: <chain.toml torrent hash>}Discovery Flow
"chain-toml"ENR entry from peersFrozenTxchain.tomlvia BitTorrent using the info-hashUpdate Propagation
When snapshots advance to a new max step:
chain.tomlgenerated (superset of previous)Implementation Plan
Phase 1: ENR Entry Type
New file:
p2p/enr/chain_toml.go+ testsDefine
ChainTomlENR entry with RLP encode/decode following existing patterns inp2p/enr/entries.go.Phase 2: chain.toml Generation (Parallel to preverified.toml)
New file:
db/downloader/chaintoml.goNo existing files modified. Core functions:
GenerateChainToml(snapDir)— scan.torrentfiles, produce TOML bytesSaveChainToml(snapDir, tomlBytes)— atomic writeLoadChainToml(snapDir)— read local fileBuildChainTomlTorrent(snapDir, torrentFS)— build.torrent, return info-hashPublishChainToml(snapDir, torrentFS, enrUpdater)— orchestrate generate → save → torrent → ENRPhase 3: Torrent Creation + ENR Advertisement
Add
enrUpdatercallback to Downloader struct. Wire innode/eth/backend.go.Trigger points (additive hooks after existing logic):
SaveSnapshotHashesinstage_snapshots.goseeder.Seed()inMergeBlocksonMergecallbackPhase 4: P2P Discovery + Download
New file:
db/downloader/p2p_chaintoml.goDiscoverChainToml(discv5)— iterate peers, find highest FrozenTxDownloadChainToml(torrentClient, infoHash)— download chain.toml via BitTorrentRuns as a separate path from
LoadSnapshotsHashes— no modification to existing startup.Phase 5: Background Update Loop
Goroutine in Downloader, gated by feature flag. Every 5 minutes: discover peers, download newer chain.toml if available, validate append-only property, update local file + ENR.
Phase 6: Graceful Removal
Entire feature removable by: deleting chain.toml + its .torrent, restarting without feature flag, deleting new source files. No existing code affected.
Files
New files
p2p/enr/chain_toml.gop2p/enr/chain_toml_test.godb/downloader/chaintoml.godb/downloader/chaintoml_test.godb/downloader/p2p_chaintoml.goModified files (additive only)
node/eth/backend.goexecution/stagedsync/stage_snapshots.goPublishChainTomlafterSaveSnapshotHashesdb/snapshotsync/freezeblocks/block_snapshots.goPublishChainTomlinonMergedb/downloader/downloader.goenrUpdaterfield + background loopUntouched (explicitly)
db/datadir/dirs.go—PreverifiedFileNamestays as"preverified.toml"db/downloader/downloadercfg/downloadercfg.go—LoadSnapshotsHashes/SaveSnapshotHashesunchangeddb/snapcfg/util.go—Cfg.Localflag unchangeddb/snapshotsync/snapshotsync.go—SyncSnapshotsLocal check unchangedAcceptance Criteria
chain.tomlfrom local.torrentfiles{FrozenTx, InfoHash}in ENR on startupchain.tomlchain.tomlvia BitTorrentchain.tomlwhen peers have newer version