diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b5802f51e7..1ebca066c8 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -77,6 +77,7 @@ var ( utils.BlobPoolDataCapFlag, utils.BlobPoolPriceBumpFlag, utils.SyncModeFlag, + utils.SnapSkipReceiptsFlag, utils.SyncTargetFlag, utils.ExitWhenSyncedFlag, utils.GCModeFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8b06772d87..274cdeaf09 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -285,6 +285,15 @@ var ( Value: ethconfig.Defaults.SyncMode.String(), Category: flags.StateCategory, } + SnapSkipReceiptsFlag = &cli.BoolFlag{ + Name: "snap.skipreceipts", + Usage: "Skip the historical receipt backfill during snap sync, reconstructing state only. " + + "Lets a node snap-sync from a peer that does not retain historical receipts (e.g. a state-only / " + + "snapshot-restored source). The resulting node has no receipts for the pre-sync range, so " + + "eth_getLogs / eth_getTransactionReceipt / eth_getBlockReceipts return a pruned-history error there. " + + "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")`, @@ -1791,6 +1800,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } } + if ctx.IsSet(SnapSkipReceiptsFlag.Name) { + cfg.SnapSkipReceipts = ctx.Bool(SnapSkipReceiptsFlag.Name) + if cfg.SnapSkipReceipts { + log.Warn("Snap sync receipt backfill disabled via --snap.skipreceipts: state is reconstructed in full, " + + "but the node will have no receipts for the pre-sync range. eth_getLogs, eth_getTransactionReceipt and " + + "eth_getBlockReceipts will report pruned history there. Use only for benchmarking / trusted-peer setups.") + } + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheDatabaseFlag.Name) { cfg.DatabaseCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheDatabaseFlag.Name) / 100 } diff --git a/eth/backend.go b/eth/backend.go index c880e75f27..7c0271e794 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -349,15 +349,16 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Permit the downloader to use the trie cache allowance during fast sync cacheLimit := options.TrieCleanLimit + options.TrieDirtyLimit + options.SnapshotLimit if eth.handler, err = newHandler(&handlerConfig{ - NodeID: eth.p2pServer.Self().ID(), - Database: chainDb, - Chain: eth.blockchain, - TxPool: eth.txPool, - Network: networkID, - Sync: config.SyncMode, - BloomCache: uint64(cacheLimit), - RequiredBlocks: config.RequiredBlocks, - SnapV2: config.SnapV2, + NodeID: eth.p2pServer.Self().ID(), + Database: chainDb, + Chain: eth.blockchain, + TxPool: eth.txPool, + Network: networkID, + Sync: config.SyncMode, + SnapSkipReceipts: config.SnapSkipReceipts, + BloomCache: uint64(cacheLimit), + RequiredBlocks: config.RequiredBlocks, + SnapV2: config.SnapV2, }); err != nil { return nil, err } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 876115ea97..a6990959ab 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -265,6 +265,12 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, drop return dl } +// SetSkipReceipts skips the historical receipt backfill during snap sync, +// reconstructing state only. Must be set before synchronisation starts. +func (d *Downloader) SetSkipReceipts(v bool) { + d.queue.skipReceipts = 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. diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 585906b8bd..85e4b85c0c 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -150,6 +150,10 @@ type queue struct { active *sync.Cond closed bool + // skipReceipts skips scheduling and fetching receipts during snap sync; + // state is still reconstructed in full. + skipReceipts bool + logTime time.Time // Time instance when status was last reported } @@ -272,7 +276,7 @@ func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uin q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) } // Queue for receipt retrieval - if q.mode == ethconfig.SnapSync && !header.EmptyReceipts() { + if q.mode == ethconfig.SnapSync && !q.skipReceipts && !header.EmptyReceipts() { if _, ok := q.receiptTaskPool[hash]; ok { log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash) } else { @@ -425,7 +429,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common // we can ask the resultcache if this header is within the // "prioritized" segment of blocks. If it is not, we need to throttle - stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == ethconfig.SnapSync) + stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == ethconfig.SnapSync && !q.skipReceipts) if stale { // Don't put back in the task queue, this item has already been // delivered upstream diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index c7e8a0d1d6..37718eae4a 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -195,6 +195,28 @@ func TestBasics(t *testing.T) { } } +// TestSkipReceipts verifies that skipReceipts schedules bodies in full but no +// receipt tasks. +func TestSkipReceipts(t *testing.T) { + q := newQueue(10, 10) + q.skipReceipts = true + q.Prepare(1, SnapSync) + + headers := chain.headers() + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + q.Schedule(headers, hashes, 1) + + if got, exp := q.PendingBodies(), chain.Len(); got != exp { + t.Errorf("wrong pending block count, got %d, exp %d", got, exp) + } + if got, exp := q.PendingReceipts(), 0; got != exp { + t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) + } +} + func TestEmptyBlocks(t *testing.T) { numOfBlocks := len(emptyChain.blocks) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index c6f2fadde9..a86857378b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -95,6 +95,10 @@ type Config struct { NetworkId uint64 SyncMode SyncMode + // SnapSkipReceipts skips the historical receipt backfill during snap sync, + // reconstructing state only. + SnapSkipReceipts bool + // HistoryMode configures chain history retention. HistoryMode history.HistoryMode diff --git a/eth/handler.go b/eth/handler.go index 4a9573efbe..be0e42bdb5 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -101,15 +101,16 @@ type txPool interface { // handlerConfig is the collection of initialization parameters to create a full // node network handler. type handlerConfig struct { - NodeID enode.ID // P2P node ID used for tx propagation topology - Database ethdb.Database // Database for direct sync insertions - Chain *core.BlockChain // Blockchain to serve data from - TxPool txPool // Transaction pool to propagate from - Network uint64 // Network identifier to advertise - Sync ethconfig.SyncMode // Whether to snap or full sync - 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 + NodeID enode.ID // P2P node ID used for tx propagation topology + Database ethdb.Database // Database for direct sync insertions + Chain *core.BlockChain // Blockchain to serve data from + TxPool txPool // Transaction pool to propagate from + Network uint64 // Network identifier to advertise + Sync ethconfig.SyncMode // Whether to snap or full sync + SnapSkipReceipts bool // Skip historical receipt backfill during snap sync (state-only) + 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 } type handler struct { @@ -160,6 +161,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.SetSkipReceipts(config.SnapSkipReceipts) // 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) {