mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-20 11:46:44 +00:00
eth/downloader, cmd: add --snap.exactpivot for exact-head snap sync
Add an opt-in --snap.exactpivot flag that pins the snap sync pivot to the exact announced head instead of head-fsMinFullBlocks, so the reconstructed state root equals stateRoot(head). Snap sync normally pivots at head-64 and full-processes the last 64 blocks to reach the head, which requires the serving peer to retain the state at head-64. A peer that only holds a single, fixed head state (for example a frozen / snapshot-restored source used for reproducible benchmarking) cannot serve head-64: the pivot account-range request fails and the only peer is dropped. With the flag set the pivot is the head itself, which such a peer does serve. Pinning the pivot to the exact head is only safe against a trusted peer that serves a fixed, non-reorging head; on a live network the head can reorg beneath the pivot. The flag is therefore intended for state-growth benchmarking and trusted-peer setups, defaults to false, and leaves default behaviour unchanged. This mirrors the static snap pivot Nethermind already supports.
This commit is contained in:
parent
7f3afa1b71
commit
b9e6aef8ab
8 changed files with 81 additions and 1 deletions
|
|
@ -76,6 +76,7 @@ var (
|
|||
utils.BlobPoolDataCapFlag,
|
||||
utils.BlobPoolPriceBumpFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.SnapExactPivotFlag,
|
||||
utils.SyncTargetFlag,
|
||||
utils.ExitWhenSyncedFlag,
|
||||
utils.GCModeFlag,
|
||||
|
|
|
|||
|
|
@ -280,6 +280,15 @@ var (
|
|||
Value: ethconfig.Defaults.SyncMode.String(),
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
SnapExactPivotFlag = &cli.BoolFlag{
|
||||
Name: "snap.exactpivot",
|
||||
Usage: "Pin the snap sync pivot to the exact announced head instead of head-64, so the downloaded " +
|
||||
"state root equals stateRoot(head). Only safe against a trusted peer that serves a fixed, " +
|
||||
"non-reorging head (e.g. a frozen / snapshot-restored source); on a live network the head can reorg " +
|
||||
"beneath the pivot and corrupt the synced state. Intended for state-growth benchmarking and " +
|
||||
"trusted-peer setups, not general mainnet operation.",
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
GCModeFlag = &cli.StringFlag{
|
||||
Name: "gcmode",
|
||||
Usage: `Blockchain garbage collection mode ("full", "archive")`,
|
||||
|
|
@ -1757,6 +1766,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet(SnapExactPivotFlag.Name) {
|
||||
cfg.SnapExactPivot = ctx.Bool(SnapExactPivotFlag.Name)
|
||||
if cfg.SnapExactPivot {
|
||||
log.Warn("Snap sync pivot pinned to the exact head via --snap.exactpivot: only safe against a " +
|
||||
"trusted peer serving a fixed, non-reorging head. On a live network a reorg beneath the pivot " +
|
||||
"can corrupt the synced state. Use only for benchmarking / trusted-peer setups.")
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet(ChainHistoryFlag.Name) {
|
||||
value := ctx.String(ChainHistoryFlag.Name)
|
||||
if err := cfg.HistoryMode.UnmarshalText([]byte(value)); err != nil {
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
TxPool: eth.txPool,
|
||||
Network: networkID,
|
||||
Sync: config.SyncMode,
|
||||
SnapExactPivot: config.SnapExactPivot,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
RequiredBlocks: config.RequiredBlocks,
|
||||
SnapV2: config.SnapV2,
|
||||
|
|
|
|||
|
|
@ -306,6 +306,9 @@ func (d *Downloader) fetchHeaders(from uint64) error {
|
|||
// Retrieve the next pivot header, either from skeleton chain
|
||||
// or the filled chain
|
||||
number := head.Number.Uint64() - uint64(fsMinFullBlocks)
|
||||
if d.exactPivot {
|
||||
number = head.Number.Uint64()
|
||||
}
|
||||
|
||||
log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number)
|
||||
if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil {
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ type Downloader struct {
|
|||
// State sync
|
||||
pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root
|
||||
pivotLock sync.RWMutex // Lock protecting pivot header reads from updates
|
||||
exactPivot bool // Pin the snap pivot to the exact head instead of head-fsMinFullBlocks
|
||||
|
||||
snapSyncer snap.Syncer // snap/1 or snap/2 state syncer, selected at construction
|
||||
stateSyncStart chan *stateSync
|
||||
|
|
@ -261,6 +262,13 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, drop
|
|||
return dl
|
||||
}
|
||||
|
||||
// SetExactPivot pins the snap sync pivot to the exact announced head instead of
|
||||
// head-fsMinFullBlocks, so the downloaded state root equals stateRoot(head).
|
||||
// Must be set before synchronisation starts.
|
||||
func (d *Downloader) SetExactPivot(v bool) {
|
||||
d.exactPivot = v
|
||||
}
|
||||
|
||||
// Progress retrieves the synchronisation boundaries, specifically the origin
|
||||
// block where synchronisation started at (may have failed/suspended); the block
|
||||
// or header sync is currently at; and the latest known block which the sync targets.
|
||||
|
|
@ -472,7 +480,12 @@ func (d *Downloader) syncToHead() (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if latest.Number.Uint64() > uint64(fsMinFullBlocks) {
|
||||
if d.exactPivot {
|
||||
// With --snap.exactpivot, pin the pivot to the exact announced head
|
||||
// instead of head-fsMinFullBlocks, so the synced state root equals
|
||||
// stateRoot(head). Safe only against a fixed, non-reorging head.
|
||||
pivot = latest
|
||||
} else if latest.Number.Uint64() > uint64(fsMinFullBlocks) {
|
||||
number := latest.Number.Uint64() - uint64(fsMinFullBlocks)
|
||||
|
||||
// Retrieve the pivot header from the skeleton chain segment but
|
||||
|
|
|
|||
|
|
@ -683,6 +683,43 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestBeaconSyncSnapExactPivot verifies that SetExactPivot pins the snap sync
|
||||
// pivot to the exact announced head instead of head-fsMinFullBlocks, so the
|
||||
// synced state root equals stateRoot(head).
|
||||
func TestBeaconSyncSnapExactPivot(t *testing.T) {
|
||||
success := make(chan struct{})
|
||||
tester := newTesterWithNotification(t, SnapSync, func() {
|
||||
close(success)
|
||||
})
|
||||
defer tester.terminate()
|
||||
tester.downloader.SetExactPivot(true)
|
||||
|
||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
||||
tester.newPeer("peer", eth.ETH69, chain.blocks[1:])
|
||||
|
||||
head := chain.blocks[len(chain.blocks)-1].Header()
|
||||
if err := tester.downloader.BeaconSync(head, nil); err != nil {
|
||||
t.Fatalf("failed to beacon sync chain: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-success:
|
||||
if bs := int(tester.chain.CurrentBlock().Number.Uint64()) + 1; bs != len(chain.blocks) {
|
||||
t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, len(chain.blocks))
|
||||
}
|
||||
// With the exact-pivot flag the snap pivot must equal the head, not
|
||||
// head-fsMinFullBlocks as in the default beacon snap sync.
|
||||
pivot := rawdb.ReadLastPivotNumber(tester.downloader.stateDB)
|
||||
if pivot == nil {
|
||||
t.Fatalf("no snap pivot recorded")
|
||||
}
|
||||
if *pivot != head.Number.Uint64() {
|
||||
t.Fatalf("pivot mismatch: have %d, want %d (exact head)", *pivot, head.Number.Uint64())
|
||||
}
|
||||
case <-time.NewTimer(time.Second * 3).C:
|
||||
t.Fatalf("failed to sync chain in three seconds")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that synchronisation progress (origin block number, current block number
|
||||
// and highest block number) is tracked and updated correctly.
|
||||
func TestSyncProgressFull(t *testing.T) { testSyncProgress(t, eth.ETH69, FullSync) }
|
||||
|
|
|
|||
|
|
@ -94,6 +94,11 @@ type Config struct {
|
|||
NetworkId uint64
|
||||
SyncMode SyncMode
|
||||
|
||||
// SnapExactPivot pins the snap sync pivot to the exact announced head
|
||||
// instead of head-fsMinFullBlocks, so the downloaded state root equals
|
||||
// stateRoot(head). Intended for syncing a fixed state from a trusted peer.
|
||||
SnapExactPivot bool
|
||||
|
||||
// HistoryMode configures chain history retention.
|
||||
HistoryMode history.HistoryMode
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ type handlerConfig struct {
|
|||
TxPool txPool // Transaction pool to propagate from
|
||||
Network uint64 // Network identifier to advertise
|
||||
Sync ethconfig.SyncMode // Whether to snap or full sync
|
||||
SnapExactPivot bool // Pin the snap pivot to the exact head instead of head-fsMinFullBlocks
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
SnapV2 bool // Whether to advertise and sync via the snap/2 protocol
|
||||
|
|
@ -158,6 +159,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
}
|
||||
// Construct the downloader (long sync)
|
||||
h.downloader = downloader.New(config.Database, config.Sync, h.chain, h.removePeer, h.enableSyncedFeatures, config.SnapV2)
|
||||
h.downloader.SetExactPivot(config.SnapExactPivot)
|
||||
|
||||
// If snap sync is requested but snapshots are disabled, fail loudly
|
||||
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue