mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 12:16: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.BlobPoolDataCapFlag,
|
||||||
utils.BlobPoolPriceBumpFlag,
|
utils.BlobPoolPriceBumpFlag,
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
|
utils.SnapExactPivotFlag,
|
||||||
utils.SyncTargetFlag,
|
utils.SyncTargetFlag,
|
||||||
utils.ExitWhenSyncedFlag,
|
utils.ExitWhenSyncedFlag,
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,15 @@ var (
|
||||||
Value: ethconfig.Defaults.SyncMode.String(),
|
Value: ethconfig.Defaults.SyncMode.String(),
|
||||||
Category: flags.StateCategory,
|
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{
|
GCModeFlag = &cli.StringFlag{
|
||||||
Name: "gcmode",
|
Name: "gcmode",
|
||||||
Usage: `Blockchain garbage collection mode ("full", "archive")`,
|
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) {
|
if ctx.IsSet(ChainHistoryFlag.Name) {
|
||||||
value := ctx.String(ChainHistoryFlag.Name)
|
value := ctx.String(ChainHistoryFlag.Name)
|
||||||
if err := cfg.HistoryMode.UnmarshalText([]byte(value)); err != nil {
|
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,
|
TxPool: eth.txPool,
|
||||||
Network: networkID,
|
Network: networkID,
|
||||||
Sync: config.SyncMode,
|
Sync: config.SyncMode,
|
||||||
|
SnapExactPivot: config.SnapExactPivot,
|
||||||
BloomCache: uint64(cacheLimit),
|
BloomCache: uint64(cacheLimit),
|
||||||
RequiredBlocks: config.RequiredBlocks,
|
RequiredBlocks: config.RequiredBlocks,
|
||||||
SnapV2: config.SnapV2,
|
SnapV2: config.SnapV2,
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,9 @@ func (d *Downloader) fetchHeaders(from uint64) error {
|
||||||
// Retrieve the next pivot header, either from skeleton chain
|
// Retrieve the next pivot header, either from skeleton chain
|
||||||
// or the filled chain
|
// or the filled chain
|
||||||
number := head.Number.Uint64() - uint64(fsMinFullBlocks)
|
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)
|
log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number)
|
||||||
if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil {
|
if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil {
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ type Downloader struct {
|
||||||
// State sync
|
// State sync
|
||||||
pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root
|
pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root
|
||||||
pivotLock sync.RWMutex // Lock protecting pivot header reads from updates
|
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
|
snapSyncer snap.Syncer // snap/1 or snap/2 state syncer, selected at construction
|
||||||
stateSyncStart chan *stateSync
|
stateSyncStart chan *stateSync
|
||||||
|
|
@ -261,6 +262,13 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, drop
|
||||||
return dl
|
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
|
// Progress retrieves the synchronisation boundaries, specifically the origin
|
||||||
// block where synchronisation started at (may have failed/suspended); the block
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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)
|
number := latest.Number.Uint64() - uint64(fsMinFullBlocks)
|
||||||
|
|
||||||
// Retrieve the pivot header from the skeleton chain segment but
|
// 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
|
// Tests that synchronisation progress (origin block number, current block number
|
||||||
// and highest block number) is tracked and updated correctly.
|
// and highest block number) is tracked and updated correctly.
|
||||||
func TestSyncProgressFull(t *testing.T) { testSyncProgress(t, eth.ETH69, FullSync) }
|
func TestSyncProgressFull(t *testing.T) { testSyncProgress(t, eth.ETH69, FullSync) }
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,11 @@ type Config struct {
|
||||||
NetworkId uint64
|
NetworkId uint64
|
||||||
SyncMode SyncMode
|
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 configures chain history retention.
|
||||||
HistoryMode history.HistoryMode
|
HistoryMode history.HistoryMode
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ type handlerConfig struct {
|
||||||
TxPool txPool // Transaction pool to propagate from
|
TxPool txPool // Transaction pool to propagate from
|
||||||
Network uint64 // Network identifier to advertise
|
Network uint64 // Network identifier to advertise
|
||||||
Sync ethconfig.SyncMode // Whether to snap or full sync
|
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
|
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
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
|
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)
|
// Construct the downloader (long sync)
|
||||||
h.downloader = downloader.New(config.Database, config.Sync, h.chain, h.removePeer, h.enableSyncedFeatures, config.SnapV2)
|
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 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) {
|
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue