From e48ede038dab9683fc84476d496614fbffbefb53 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Sun, 8 Feb 2026 00:48:49 +0100 Subject: [PATCH] eth: disable snapshots for partial state nodes Partial state nodes don't need snapshots since account data is read directly from the trie (which is small enough for fast lookups) and BAL-based block processing never uses snapshots. - Set SnapshotCache to 0 when partial state is enabled (flags.go) - Allow snap sync without snapshots for partial state mode (handler.go) - Add nil-check for Snapshots() in snap request handlers to prevent panics when serving HashScheme peers (snap/handler.go) --- cmd/utils/flags.go | 11 +++++++++-- eth/handler.go | 9 +++++++-- eth/protocols/snap/handler.go | 12 ++++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 80639db45c..ab2c16e329 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1891,6 +1891,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(PartialStateChainRetentionFlag.Name) { cfg.PartialState.ChainRetention = ctx.Uint64(PartialStateChainRetentionFlag.Name) } + // Partial state nodes don't need snapshots — account data is read + // directly from the trie (which is small enough for fast lookups), + // and BAL-based block processing never uses snapshots. + if cfg.PartialState.Enabled { + cfg.SnapshotCache = 0 + } // Parse transaction history flag, if user is still using legacy config // file with 'TxLookupLimit' configured, copy the value to 'TransactionHistory'. if cfg.TransactionHistory == ethconfig.Defaults.TransactionHistory && cfg.TxLookupLimit != ethconfig.Defaults.TxLookupLimit { @@ -1946,8 +1952,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.RangeLimit = ctx.Uint64(RPCGlobalRangeLimitFlag.Name) } if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { - // If snap-sync is requested, this flag is also required - if cfg.SyncMode == ethconfig.SnapSync { + // If snap-sync is requested, this flag is also required (unless + // partial state mode is active, which disables snapshots entirely). + if cfg.SyncMode == ethconfig.SnapSync && !cfg.PartialState.Enabled { if !ctx.Bool(SnapshotFlag.Name) { log.Warn("Snap sync requested, enabling --snapshot") } diff --git a/eth/handler.go b/eth/handler.go index 546ef1f197..bde190c758 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -168,9 +168,14 @@ func newHandler(config *handlerConfig) (*handler, error) { // Construct the downloader (long sync) h.downloader = downloader.New(config.Database, config.Sync, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures, config.PartialFilter, config.ChainRetention) - // If snap sync is requested but snapshots are disabled, fail loudly + // If snap sync is requested but snapshots are disabled, fail loudly. + // Partial state nodes are an exception: they disable snapshots intentionally + // (account data is read directly from the trie, BAL processing never uses snapshots). if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) { - return nil, errors.New("snap sync not supported with snapshots disabled") + if !config.Chain.SupportsPartialState() { + return nil, errors.New("snap sync not supported with snapshots disabled") + } + log.Info("Snap sync with snapshots disabled (partial state mode)") } fetchTx := func(peer string, hashes []common.Hash) error { p := h.peers.peer(peer) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 071a0419fb..832e2396c4 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -338,7 +338,11 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac var it snapshot.AccountIterator if chain.TrieDB().Scheme() == rawdb.HashScheme { // The snapshot is assumed to be available in hash mode if - // the SNAP protocol is enabled. + // the SNAP protocol is enabled. Partial state nodes disable + // snapshots, so bail out gracefully if unavailable. + if chain.Snapshots() == nil { + return nil, nil + } it, err = chain.Snapshots().AccountIterator(req.Root, req.Origin) } else { it, err = chain.TrieDB().AccountIterator(req.Root, req.Origin) @@ -430,7 +434,11 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP // This can be removed once the hash scheme is deprecated. if chain.TrieDB().Scheme() == rawdb.HashScheme { // The snapshot is assumed to be available in hash mode if - // the SNAP protocol is enabled. + // the SNAP protocol is enabled. Partial state nodes disable + // snapshots, so bail out gracefully if unavailable. + if chain.Snapshots() == nil { + return nil, nil + } it, err = chain.Snapshots().StorageIterator(req.Root, account, origin) } else { it, err = chain.TrieDB().StorageIterator(req.Root, account, origin)