From 65992dd82c7afa2c9e6ac12299d5ea093cc8f716 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 30 Jun 2022 11:50:19 +0800 Subject: [PATCH 1/8] support discarding ancient totally --- core/rawdb/accessors_chain.go | 18 +++ core/rawdb/chain_freezer.go | 19 +-- core/rawdb/chain_thrower.go | 283 ++++++++++++++++++++++++++++++++++ core/rawdb/database.go | 50 +++++- core/rawdb/schema.go | 3 + eth/backend.go | 2 +- node/node.go | 34 +++- 7 files changed, 388 insertions(+), 21 deletions(-) create mode 100644 core/rawdb/chain_thrower.go diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 8ea2e2ca72..4fbba22b55 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -168,6 +168,24 @@ func DeleteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash) { } } +// ReadAncientThrowLastBlock retrieves the hash of the finalized block. +func ReadAncientThrowLastBlockNumber(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(ancientThrowLastBlock) + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteAncientThrowLastBlock stores the hash of the finalized block. +func WriteAncientThrowLastBlockNumber(db ethdb.KeyValueWriter, number uint64) { + enc := encodeBlockNumber(number) + if err := db.Put(ancientThrowLastBlock, enc); err != nil { + log.Crit("Failed to store last throwed ancient block's hash", "err", err) + } +} + // ReadHeadHeaderHash retrieves the hash of the current canonical head header. func ReadHeadHeaderHash(db ethdb.KeyValueReader) common.Hash { data, _ := db.Get(headHeaderKey) diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index 6f602f7b51..4c49db2748 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -37,10 +37,6 @@ const ( // freezerBatchLimit is the maximum number of blocks to freeze in one batch // before doing an fsync and deleting it from the key-value store. freezerBatchLimit = 30000 - - // cleanerRecheckInterval is the frequency to check the freezer database to - // find the bloks that might be pruned - cleanerRecheckInterval = 1 * time.Minute ) // chainFreezer is a wrapper of freezer with additional chain freezing feature. @@ -52,10 +48,6 @@ type chainFreezer struct { // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) - // ancientRecentLimit Number of recent blocks to keep in ancient db - // if not 0, blocks older than `HEAD - ancientRecentLimit` will be purged from ancient db - ancientRecentLimit uint64 - *Freezer quit chan struct{} wg sync.WaitGroup @@ -63,17 +55,16 @@ type chainFreezer struct { } // newChainFreezer initializes the freezer for ancient chain data. -func newChainFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool, ancientRecentLimit uint64) (*chainFreezer, error) { +func newChainFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*chainFreezer, error) { freezer, err := NewFreezer(datadir, namespace, readonly, maxTableSize, tables) if err != nil { return nil, err } return &chainFreezer{ - Freezer: freezer, - ancientRecentLimit: ancientRecentLimit, - threshold: params.FullImmutabilityThreshold, - quit: make(chan struct{}), - trigger: make(chan chan struct{}), + Freezer: freezer, + threshold: params.FullImmutabilityThreshold, + quit: make(chan struct{}), + trigger: make(chan chan struct{}), }, nil } diff --git a/core/rawdb/chain_thrower.go b/core/rawdb/chain_thrower.go new file mode 100644 index 0000000000..1433cd5746 --- /dev/null +++ b/core/rawdb/chain_thrower.go @@ -0,0 +1,283 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +const ( + // freezerRecheckInterval is the frequency to check the key-value database for + // chain progression that might permit new blocks to be frozen into immutable + // storage. + nofreezerRecheckInterval = time.Minute + + // freezerBatchLimit is the maximum number of blocks to freeze in one batch + // before doing an fsync and deleting it from the key-value store. + nofreezerBatchLimit = 30000 +) + +// chainThrower is a wrapper of freezer with additional chain freezing feature. +// The background thread will keep moving ancient chain segments from key-value +// database to flat files for saving space on live database. +type chainThrower struct { + nofreezedb + + // WARNING: The `threshold` field is accessed atomically. On 32 bit platforms, only + // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, + // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). + threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) + + quit chan struct{} + wg sync.WaitGroup + trigger chan chan struct{} // Manual blocking freeze trigger, test determinism +} + +// newChainFreezer initializes the freezer for ancient chain data. +func newChainThrower(datadir string, namespace string, readonly bool) (*chainThrower, error) { + return &chainThrower{ + threshold: params.FullImmutabilityThreshold, + quit: make(chan struct{}), + trigger: make(chan chan struct{}), + }, nil +} + +// Close closes the chain freezer instance and terminates the background thread. +func (f *chainThrower) Close() error { + select { + case <-f.quit: + default: + close(f.quit) + } + f.wg.Wait() + return nil +} + +// freeze is a background thread that periodically checks the blockchain for any +// import progress and moves ancient data from the fast database into the freezer. +// +// This functionality is deliberately broken off from block importing to avoid +// incurring additional data shuffling delays on block propagation. +func (f *chainThrower) throw(db ethdb.KeyValueStore) { + nfdb := &nofreezedb{KeyValueStore: db} + + var ( + backoff bool + triggered chan struct{} // Used in tests + ) + for { + select { + case <-f.quit: + log.Info("Thrower shutting down") + return + default: + } + if backoff { + // If we were doing a manual trigger, notify it + if triggered != nil { + triggered <- struct{}{} + triggered = nil + } + select { + case <-time.NewTimer(freezerRecheckInterval).C: + backoff = false + case triggered = <-f.trigger: + backoff = false + case <-f.quit: + return + } + } + // Retrieve the freezing threshold. + hash := ReadHeadBlockHash(nfdb) + if hash == (common.Hash{}) { + log.Debug("Current full block hash unavailable") // new chain, empty database + backoff = true + continue + } + number := ReadHeaderNumber(nfdb, hash) + threshold := atomic.LoadUint64(&f.threshold) + last := ReadAncientThrowLastBlockNumber(db) + if last == nil { + last = new(uint64) + } + switch { + case number == nil: + log.Error("Current full block number unavailable", "hash", hash) + backoff = true + continue + + case *number < threshold: + log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) + backoff = true + continue + + case *number-threshold <= *last: + log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "last", last) + backoff = true + continue + } + + head := ReadHeader(nfdb, hash, *number) + if head == nil { + log.Error("Current full block unavailable", "number", *number, "hash", hash) + backoff = true + continue + } + + // Seems we have data ready to be frozen, process in usable batches + var ( + start = time.Now() + first = *last + limit = *number - threshold + ) + if limit-first > freezerBatchLimit { + limit = first + freezerBatchLimit + } + ancients, err := f.throwRange(nfdb, first, limit) + if err != nil { + log.Error("Error in block freeze operation", "err", err) + backoff = true + continue + } + + // Wipe out all data from the active database + batch := db.NewBatch() + for i := 0; i < len(ancients); i++ { + // Always keep the genesis block in active database + if first+uint64(i) != 0 { + DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) + DeleteCanonicalHash(batch, first+uint64(i)) + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to throw ancient blocks", "err", err) + } + batch.Reset() + + // record + WriteAncientThrowLastBlockNumber(db, limit) + + // Wipe out side chains also and track dangling side chains + var dangling []common.Hash + for number := first; number < limit; number++ { + // Always keep the genesis block in active database + if number != 0 { + dangling = ReadAllHashes(db, number) + for _, hash := range dangling { + log.Trace("Deleting side chain", "number", number, "hash", hash) + DeleteBlock(batch, hash, number) + } + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to delete frozen side blocks", "err", err) + } + batch.Reset() + + // Step into the future and delete and dangling side chains + if limit > 0 { + tip := limit + for len(dangling) > 0 { + drop := make(map[common.Hash]struct{}) + for _, hash := range dangling { + log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) + drop[hash] = struct{}{} + } + children := ReadAllHashes(db, tip) + for i := 0; i < len(children); i++ { + // Dig up the child and ensure it's dangling + child := ReadHeader(nfdb, children[i], tip) + if child == nil { + log.Error("Missing dangling header", "number", tip, "hash", children[i]) + continue + } + if _, ok := drop[child.ParentHash]; !ok { + children = append(children[:i], children[i+1:]...) + i-- + continue + } + // Delete all block data associated with the child + log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) + DeleteBlock(batch, children[i], tip) + } + dangling = children + tip++ + } + if err := batch.Write(); err != nil { + log.Crit("Failed to delete dangling side blocks", "err", err) + } + } + + // Log something friendly for the user + context := []interface{}{ + "blocks", limit - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", limit - 1, + } + if n := len(ancients); n > 0 { + context = append(context, []interface{}{"hash", ancients[n-1]}...) + } + log.Info("Deep throw chain segment", context...) + + // Avoid database thrashing with tiny writes + if limit-first < freezerBatchLimit { + backoff = true + } + } +} + +func (f *chainThrower) throwRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { + hashes = make([]common.Hash, 0, limit-number) + + for ; number <= limit; number++ { + // Retrieve all the components of the canonical block. + hash := ReadCanonicalHash(nfdb, number) + if hash == (common.Hash{}) { + log.Error("canonical hash missing, can't freeze block %d", number) + continue + } + header := ReadHeaderRLP(nfdb, hash, number) + if len(header) == 0 { + log.Error("block header missing, can't freeze block %d", number) + continue + } + body := ReadBodyRLP(nfdb, hash, number) + if len(body) == 0 { + log.Error("block body missing, can't freeze block %d", number) + continue + } + receipts := ReadReceiptsRLP(nfdb, hash, number) + if len(receipts) == 0 { + log.Error("block receipts missing, can't freeze block %d", number) + continue + } + td := ReadTdRLP(nfdb, hash, number) + if len(td) == 0 { + log.Error("total difficulty missing, can't freeze block %d", number) + continue + } + + hashes = append(hashes, hash) + } + + return hashes, err +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 6cb5f69f99..4960e439cd 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -165,9 +165,9 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { // NewDatabaseWithFreezer creates a high level database on top of a given key- // value data store with a freezer moving immutable chain segments into cold // storage. -func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool, ancientRecentLimit uint64) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newChainFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy, ancientRecentLimit) + frdb, err := newChainFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy) if err != nil { return nil, err } @@ -194,7 +194,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // validate in this method. If, however, the genesis hash is not nil, compare // it to the freezer content. // TODO consider deprecate this check - if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 && frdb.ancientRecentLimit == 0 { + if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { if frozen, _ := frdb.Ancients(); frozen > 0 { // If the freezer already contains something, ensure that the genesis blocks // match, otherwise we might mix up freezers across chains and destroy both @@ -250,6 +250,29 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }, nil } +// NewDatabaseWithThrower creates a high level database on top of a given key- +// value data store with a freezer moving immutable chain segments into cold +// storage. +func NewDatabaseWithThrower(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { + // Create the idle freezer instance + throwdb, err := newChainThrower(freezer, namespace, readonly) + if err != nil { + return nil, err + } + + // Thrower is consistent with the key-value database, permit combining the two + throwdb.wg.Add(1) + go func() { + throwdb.throw(db) + throwdb.wg.Done() + }() + + return &freezerdb{ + KeyValueStore: db, + AncientStore: throwdb, + }, nil +} + // NewMemoryDatabase creates an ephemeral in-memory key-value database without a // freezer moving immutable chain segments into cold storage. func NewMemoryDatabase() ethdb.Database { @@ -275,12 +298,29 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a // freezer moving immutable chain segments into cold storage. -func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool, ancientRecentLimit uint64) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, ancientRecentLimit) + + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly) + if err != nil { + kvdb.Close() + return nil, err + } + return frdb, nil +} + +// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a +// freezer moving immutable chain segments into cold storage. +func NewLevelDBDatabaseWithThrower(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { + kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) + if err != nil { + return nil, err + } + + frdb, err := NewDatabaseWithThrower(kvdb, freezer, namespace, readonly) if err != nil { kvdb.Close() return nil, err diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 041c9f0449..dce29a2d14 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -84,6 +84,9 @@ var ( // transitionStatusKey tracks the eth2 transition status. transitionStatusKey = []byte("eth2-transition") + // ancientThrowLastBlock tracks the last recent ancient block that has been thrown + ancientThrowLastBlock = []byte("AncientThrowLastBlock") + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td diff --git a/eth/backend.go b/eth/backend.go index 48d7d1c249..9a887ef2f0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -133,7 +133,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { ethashConfig.NotifyFull = config.Miner.NotifyFull // Assemble the Ethereum object - chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientRecentLimit) + chainDb, err := stack.OpenDatabaseWrapper("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientRecentLimit != 0) if err != nil { return nil, err } diff --git a/node/node.go b/node/node.go index c67bbd01d8..b390abdb4b 100644 --- a/node/node.go +++ b/node/node.go @@ -712,6 +712,38 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r return db, err } +// OpenDatabaseWrapper ... +func (n *Node) OpenDatabaseWrapper(name string, cache, handles int, freezer, namespace string, readonly, throwAncient bool) (ethdb.Database, error) { + n.lock.Lock() + defer n.lock.Unlock() + if n.state == closedState { + return nil, ErrNodeStopped + } + + var db ethdb.Database + var err error + if n.config.DataDir == "" { + db = rawdb.NewMemoryDatabase() + } else if !throwAncient { + root := n.ResolvePath(name) + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = n.ResolvePath(freezer) + } + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) + } else { + root := n.ResolvePath(name) + db, err = rawdb.NewLevelDBDatabaseWithThrower(root, cache, handles, freezer, namespace, readonly) + } + + if err == nil { + db = n.wrapDatabase(db) + } + return db, err +} + // OpenDatabaseWithFreezer opens an existing database with the given name (or // creates one if no previous can be found) from within the node's data directory, // also attaching a chain freezer to it that moves ancient chain data from the @@ -736,7 +768,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, case !filepath.IsAbs(freezer): freezer = n.ResolvePath(freezer) } - db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, ancientRecentLimit) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) } if err == nil { From 81157fc4a8fa60dfc36e849852f068253158b298 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 30 Jun 2022 17:05:44 +0800 Subject: [PATCH 2/8] temprorarily stash the implementation --- core/blockchain.go | 97 ++++++------------------------ core/rawdb/chain_thrower.go | 115 ++++++++++++++++++++++++++++++++---- 2 files changed, 120 insertions(+), 92 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 366782800d..8050cd8de7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -406,6 +406,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par // Start tx indexer/unindexer. if txLookupLimit != nil { bc.txLookupLimit = *txLookupLimit + if bc.cacheConfig.AncientRecentLimit != 0 { + bc.txLookupLimit = params.FullImmutabilityThreshold + } bc.wg.Add(1) go bc.maintainTxIndex(txIndexBlock) @@ -937,6 +940,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ bc.wg.Add(1) defer bc.wg.Done() + if bc.cacheConfig.AncientRecentLimit != 0 { + log.Info("throwing ancient turned on, set ancientLimit to 0 during snap sync") + ancientLimit = 0 + } + var ( ancientBlocks, liveBlocks types.Blocks ancientReceipts, liveReceipts []types.Receipts @@ -2270,38 +2278,19 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { func (bc *BlockChain) maintainTxIndex(ancients uint64) { defer bc.wg.Done() - ancientLimit := bc.cacheConfig.AncientRecentLimit - frozen, _ := bc.db.Ancients() - log.Info("maintainTxIndex", "ancientLimit", ancientLimit, "ancients", ancients, "frozen", frozen) // Before starting the actual maintenance, we need to handle a special case, // where user might init Geth with an external ancient database. If so, we // need to reindex all necessary transactions before starting to process any // pruning requests. if ancients > 0 { var from = uint64(0) - // let `from` be a "more" recent block - if ancientLimit > 0 { - if ancientLimit < ancients { - from = ancients - ancientLimit - } - if bc.txLookupLimit != 0 && bc.txLookupLimit < ancientLimit { - from = ancients - bc.txLookupLimit - } - } else { - if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit { - from = ancients - bc.txLookupLimit - } + if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit { + from = ancients - bc.txLookupLimit } - log.Info("maintainTxIndex", "ancientLimit", ancientLimit, "ancients", ancients, "from", from) rawdb.IndexTransactions(bc.db, from, ancients, bc.quit) } - if ancientLimit > 0 && (bc.txLookupLimit == 0 || bc.txLookupLimit > ancientLimit) { - log.Warn("reduece txLookupLimit to meet ancient purging purpose, as it's insane to lookup txs in purged blocks") - bc.txLookupLimit = ancientLimit - } - // indexBlocks reindexes or unindexes transactions depending on user configuration indexBlocks := func(tail *uint64, head uint64, done chan struct{}) { defer func() { done <- struct{}{} }() @@ -2344,56 +2333,9 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } } - // pruneAncient is a background thread that periodically checks the blockchain for any - // import progress and cleans ancient data from the freezer. - // - // it calculates the highest block(named `cleanTo`) that can be cleaned, then starts - // cleaning from the genesis+1 block in batches, until reaches the `cleanTo` block. - pruneAncient := func(txIndexTail *uint64, head uint64, wait, done chan struct{}) { - defer func() { done <- struct{}{} }() - // Wait till the tx index routine finishes, in case that it cannot find the ancient - // blocks for iterating transactions for unindex because we have pruned the blocks - <-wait - - start := time.Now() - - first, _ := bc.db.Tail() - last, _ := bc.db.Ancients() - ancientLimit := bc.cacheConfig.AncientRecentLimit - - if last < ancientLimit || last < first { - // It should not reach here - log.Error("prune ancient error", "last frozen", last, "first frozen", first, "ancientRecentLimit", ancientLimit) - return - } - - pruneTo := last - ancientLimit - storedSections := rawdb.ReadStoredBloomSections(bc.db) - if storedSections*params.BloomBitsBlocks-1 < pruneTo { - log.Warn("Attempt to prune the ancient blocks that bloom filter haven't finished yet, postpone to next round", "storedSections", storedSections, "pruneTo", pruneTo) - return - } - - // Double ensure we don't prune the blocks having dangling transaction indices - if txIndexTail != nil && pruneTo > *txIndexTail { - log.Warn("Attempt to prune the ancient blocks that still have tx indices, postpone to next round", "txIndexTail", *txIndexTail, "pruneTo", pruneTo) - return - } - - // truncate - bc.db.TruncateTail(pruneTo) - - // Log something friendly for the user - context := []interface{}{ - "blocks", pruneTo - first, "from block", first, "to", pruneTo, "current last block in ancient", last, "elapsed", common.PrettyDuration(time.Since(start)), - } - log.Info("Cleaned ancient chain segment", context...) - } - // Any reindexing done, start listening to chain events and moving the index window var ( - doneTx chan struct{} // For tx indexing routine, non-nil if the routine is active. - donePr chan struct{} // For prune ancient routine, non-nil if the routine is active. + done chan struct{} // For tx indexing routine, non-nil if the routine is active. headCh = make(chan ChainHeadEvent, 1) // Buffered to avoid locking up the event feed ) sub := bc.SubscribeChainHeadEvent(headCh) @@ -2405,19 +2347,16 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { for { select { case head := <-headCh: - if doneTx == nil && donePr == nil { - doneTx = make(chan struct{}) - donePr = make(chan struct{}) - go indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), doneTx) - go pruneAncient(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), doneTx, donePr) + if done == nil { + done = make(chan struct{}) + go indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), done) } - case <-donePr: - donePr = nil - doneTx = nil + case <-done: + done = nil case <-bc.quit: - if donePr != nil { + if done != nil { log.Info("Waiting background transaction indexer and ancient pruner to exit") - <-donePr + <-done } return } diff --git a/core/rawdb/chain_thrower.go b/core/rawdb/chain_thrower.go index 1433cd5746..1ceadf2771 100644 --- a/core/rawdb/chain_thrower.go +++ b/core/rawdb/chain_thrower.go @@ -28,21 +28,21 @@ import ( ) const ( - // freezerRecheckInterval is the frequency to check the key-value database for + // throwerRecheckInterval is the frequency to check the key-value database for // chain progression that might permit new blocks to be frozen into immutable // storage. - nofreezerRecheckInterval = time.Minute + throwerRecheckInterval = time.Minute - // freezerBatchLimit is the maximum number of blocks to freeze in one batch + // throwerBatchLimit is the maximum number of blocks to freeze in one batch // before doing an fsync and deleting it from the key-value store. - nofreezerBatchLimit = 30000 + throwerBatchLimit = 30000 ) // chainThrower is a wrapper of freezer with additional chain freezing feature. // The background thread will keep moving ancient chain segments from key-value // database to flat files for saving space on live database. type chainThrower struct { - nofreezedb + throwdb // WARNING: The `threshold` field is accessed atomically. On 32 bit platforms, only // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, @@ -54,6 +54,88 @@ type chainThrower struct { trigger chan chan struct{} // Manual blocking freeze trigger, test determinism } +// throwdb is a database wrapper that disables freezer data retrievals. +type throwdb struct { + ethdb.KeyValueStore +} + +// HasAncient always returns false as `throwdb` has thrown everything written to it +func (db *throwdb) HasAncient(kind string, number uint64) (bool, error) { + return false, nil +} + +// Ancient returns an empty result as `throwdb` has thrown everything written to it +func (db *throwdb) Ancient(kind string, number uint64) ([]byte, error) { + return []byte{}, nil +} + +// AncientRange returns an empty result as `throwdb` has thrown everything written to it +func (db *throwdb) AncientRange(kind string, start, max, maxByteSize uint64) ([][]byte, error) { + return [][]byte{}, nil +} + +// Ancients returns 0 as we don't have a backing chain freezer. +func (db *throwdb) Ancients() (uint64, error) { + return 0, nil +} + +// Tail returns 0 as we don't have a backing chain freezer. +func (db *throwdb) Tail() (uint64, error) { + return 0, errNotSupported +} + +// AncientSize returns an error as we don't have a backing chain freezer. +func (db *throwdb) AncientSize(kind string) (uint64, error) { + return 0, nil +} + +// ModifyAncients is not supported. +func (db *throwdb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, error) { + return 0, nil +} + +// TruncateHead returns an error as we don't have a backing chain freezer. +func (db *throwdb) TruncateHead(items uint64) error { + return nil +} + +// TruncateTail returns an error as we don't have a backing chain freezer. +func (db *throwdb) TruncateTail(items uint64) error { + return nil +} + +// Sync returns an error as we don't have a backing chain freezer. +func (db *throwdb) Sync() error { + return nil +} + +func (db *throwdb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err error) { + // Unlike other ancient-related methods, this method does not return + // errNotSupported when invoked. + // The reason for this is that the caller might want to do several things: + // 1. Check if something is in freezer, + // 2. If not, check leveldb. + // + // This will work, since the ancient-checks inside 'fn' will return errors, + // and the leveldb work will continue. + // + // If we instead were to return errNotSupported here, then the caller would + // have to explicitly check for that, having an extra clause to do the + // non-ancient operations. + return fn(db) +} + +// MigrateTable processes the entries in a given table in sequence +// converting them to a new format if they're of an old format. +func (db *throwdb) MigrateTable(kind string, convert convertLegacyFn) error { + return errNotSupported +} + +// AncientDatadir returns an error as we don't have a backing chain freezer. +func (db *throwdb) AncientDatadir() (string, error) { + return "", errNotSupported +} + // newChainFreezer initializes the freezer for ancient chain data. func newChainThrower(datadir string, namespace string, readonly bool) (*chainThrower, error) { return &chainThrower{ @@ -80,7 +162,7 @@ func (f *chainThrower) Close() error { // This functionality is deliberately broken off from block importing to avoid // incurring additional data shuffling delays on block propagation. func (f *chainThrower) throw(db ethdb.KeyValueStore) { - nfdb := &nofreezedb{KeyValueStore: db} + nfdb := &throwdb{KeyValueStore: db} var ( backoff bool @@ -100,7 +182,7 @@ func (f *chainThrower) throw(db ethdb.KeyValueStore) { triggered = nil } select { - case <-time.NewTimer(freezerRecheckInterval).C: + case <-time.NewTimer(throwerRecheckInterval).C: backoff = false case triggered = <-f.trigger: backoff = false @@ -138,6 +220,12 @@ func (f *chainThrower) throw(db ethdb.KeyValueStore) { continue } + storedSections := ReadStoredBloomSections(nfdb) + if storedSections*params.BloomBitsBlocks-1 < *last { + log.Warn("Attempt to prune the ancient blocks that bloom filter haven't finished yet, postpone to next round", "storedSections", storedSections, "pruneTo", *last) + return + } + head := ReadHeader(nfdb, hash, *number) if head == nil { log.Error("Current full block unavailable", "number", *number, "hash", hash) @@ -154,6 +242,7 @@ func (f *chainThrower) throw(db ethdb.KeyValueStore) { if limit-first > freezerBatchLimit { limit = first + freezerBatchLimit } + log.Info("schedule throwing blocks", "from", first, "to", limit) ancients, err := f.throwRange(nfdb, first, limit) if err != nil { log.Error("Error in block freeze operation", "err", err) @@ -245,34 +334,34 @@ func (f *chainThrower) throw(db ethdb.KeyValueStore) { } } -func (f *chainThrower) throwRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { +func (f *chainThrower) throwRange(nfdb *throwdb, number, limit uint64) (hashes []common.Hash, err error) { hashes = make([]common.Hash, 0, limit-number) for ; number <= limit; number++ { // Retrieve all the components of the canonical block. hash := ReadCanonicalHash(nfdb, number) if hash == (common.Hash{}) { - log.Error("canonical hash missing, can't freeze block %d", number) + log.Error("canonical hash missing, can't freeze", "block %d", number) continue } header := ReadHeaderRLP(nfdb, hash, number) if len(header) == 0 { - log.Error("block header missing, can't freeze block %d", number) + log.Error("block header missing, can't freeze", "block %d", number) continue } body := ReadBodyRLP(nfdb, hash, number) if len(body) == 0 { - log.Error("block body missing, can't freeze block %d", number) + log.Error("block body missing, can't freeze", "block %d", number) continue } receipts := ReadReceiptsRLP(nfdb, hash, number) if len(receipts) == 0 { - log.Error("block receipts missing, can't freeze block %d", number) + log.Error("block receipts missing, can't freeze", "block %d", number) continue } td := ReadTdRLP(nfdb, hash, number) if len(td) == 0 { - log.Error("total difficulty missing, can't freeze block %d", number) + log.Error("total difficulty missing, can't freeze", "block %d", number) continue } From 978827202fca8baab5771a428519aa2cf29fe4a1 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 30 Jun 2022 22:54:13 +0800 Subject: [PATCH 3/8] refine the implementation --- core/blockchain.go | 5 +- core/rawdb/chain_iterator.go | 18 +- core/rawdb/chain_thrower.go | 372 ----------------------------------- core/rawdb/database.go | 47 +---- eth/backend.go | 6 +- node/node.go | 34 +--- 6 files changed, 24 insertions(+), 458 deletions(-) delete mode 100644 core/rawdb/chain_thrower.go diff --git a/core/blockchain.go b/core/blockchain.go index 8050cd8de7..92331c6bea 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -941,7 +941,6 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ defer bc.wg.Done() if bc.cacheConfig.AncientRecentLimit != 0 { - log.Info("throwing ancient turned on, set ancientLimit to 0 during snap sync") ancientLimit = 0 } @@ -2303,7 +2302,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { rawdb.WriteTxIndexTail(bc.db, 0) } else { // Prune all stale tx indices and record the tx index tail - log.Info("Scheduled transactions unindexing", "from block", 0, "to", head-bc.txLookupLimit+1) + log.Info("Scheduled blocks & transactions unindexing", "from block", 0, "to", head-bc.txLookupLimit+1) rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit) } return @@ -2328,7 +2327,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit) } else { // Unindex a part of stale indices and forward index tail to HEAD-limit - log.Info("Scheduled transactions unindexing", "from block", *tail, "to", head-bc.txLookupLimit+1) + log.Info("Scheduled blocks & transactions unindexing", "from block", *tail, "to", head-bc.txLookupLimit+1) rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit) } } diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 21e42f42d4..4d4e42b9dc 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -84,8 +84,9 @@ func InitDatabaseFromFreezer(db ethdb.Database) { } type blockTxHashes struct { - number uint64 - hashes []common.Hash + number uint64 + blockHash common.Hash + hashes []common.Hash } // iterateTransactions iterates over all transactions in the (canon) block @@ -96,6 +97,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool // One thread sequentially reads data from db type numberRlp struct { number uint64 + hash common.Hash rlp rlp.RawValue } if to == from { @@ -118,9 +120,10 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool defer close(rlpCh) for n != end { data := ReadCanonicalBodyRLP(db, n) + hash := ReadCanonicalHash(db, n) // Feed the block to the aggregator, or abort on interrupt select { - case rlpCh <- &numberRlp{n, data}: + case rlpCh <- &numberRlp{n, hash, data}: case <-interrupt: return } @@ -151,8 +154,9 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool hashes = append(hashes, tx.Hash()) } result := &blockTxHashes{ - hashes: hashes, - number: data.number, + hashes: hashes, + blockHash: data.hash, + number: data.number, } // Feed the block to the aggregator, or abort on interrupt select { @@ -303,6 +307,10 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch nextNum = delivery.number + 1 DeleteTxLookupEntries(batch, delivery.hashes) txs += len(delivery.hashes) + // Delete all about the block + if delivery.number != 0 { // never delete the genesis block + DeleteBlock(batch, delivery.blockHash, delivery.number) + } blocks++ // If enough data was accumulated in memory or we're at the last block, dump to disk diff --git a/core/rawdb/chain_thrower.go b/core/rawdb/chain_thrower.go deleted file mode 100644 index 1ceadf2771..0000000000 --- a/core/rawdb/chain_thrower.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rawdb - -import ( - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" -) - -const ( - // throwerRecheckInterval is the frequency to check the key-value database for - // chain progression that might permit new blocks to be frozen into immutable - // storage. - throwerRecheckInterval = time.Minute - - // throwerBatchLimit is the maximum number of blocks to freeze in one batch - // before doing an fsync and deleting it from the key-value store. - throwerBatchLimit = 30000 -) - -// chainThrower is a wrapper of freezer with additional chain freezing feature. -// The background thread will keep moving ancient chain segments from key-value -// database to flat files for saving space on live database. -type chainThrower struct { - throwdb - - // WARNING: The `threshold` field is accessed atomically. On 32 bit platforms, only - // 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned, - // so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG). - threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) - - quit chan struct{} - wg sync.WaitGroup - trigger chan chan struct{} // Manual blocking freeze trigger, test determinism -} - -// throwdb is a database wrapper that disables freezer data retrievals. -type throwdb struct { - ethdb.KeyValueStore -} - -// HasAncient always returns false as `throwdb` has thrown everything written to it -func (db *throwdb) HasAncient(kind string, number uint64) (bool, error) { - return false, nil -} - -// Ancient returns an empty result as `throwdb` has thrown everything written to it -func (db *throwdb) Ancient(kind string, number uint64) ([]byte, error) { - return []byte{}, nil -} - -// AncientRange returns an empty result as `throwdb` has thrown everything written to it -func (db *throwdb) AncientRange(kind string, start, max, maxByteSize uint64) ([][]byte, error) { - return [][]byte{}, nil -} - -// Ancients returns 0 as we don't have a backing chain freezer. -func (db *throwdb) Ancients() (uint64, error) { - return 0, nil -} - -// Tail returns 0 as we don't have a backing chain freezer. -func (db *throwdb) Tail() (uint64, error) { - return 0, errNotSupported -} - -// AncientSize returns an error as we don't have a backing chain freezer. -func (db *throwdb) AncientSize(kind string) (uint64, error) { - return 0, nil -} - -// ModifyAncients is not supported. -func (db *throwdb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, error) { - return 0, nil -} - -// TruncateHead returns an error as we don't have a backing chain freezer. -func (db *throwdb) TruncateHead(items uint64) error { - return nil -} - -// TruncateTail returns an error as we don't have a backing chain freezer. -func (db *throwdb) TruncateTail(items uint64) error { - return nil -} - -// Sync returns an error as we don't have a backing chain freezer. -func (db *throwdb) Sync() error { - return nil -} - -func (db *throwdb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err error) { - // Unlike other ancient-related methods, this method does not return - // errNotSupported when invoked. - // The reason for this is that the caller might want to do several things: - // 1. Check if something is in freezer, - // 2. If not, check leveldb. - // - // This will work, since the ancient-checks inside 'fn' will return errors, - // and the leveldb work will continue. - // - // If we instead were to return errNotSupported here, then the caller would - // have to explicitly check for that, having an extra clause to do the - // non-ancient operations. - return fn(db) -} - -// MigrateTable processes the entries in a given table in sequence -// converting them to a new format if they're of an old format. -func (db *throwdb) MigrateTable(kind string, convert convertLegacyFn) error { - return errNotSupported -} - -// AncientDatadir returns an error as we don't have a backing chain freezer. -func (db *throwdb) AncientDatadir() (string, error) { - return "", errNotSupported -} - -// newChainFreezer initializes the freezer for ancient chain data. -func newChainThrower(datadir string, namespace string, readonly bool) (*chainThrower, error) { - return &chainThrower{ - threshold: params.FullImmutabilityThreshold, - quit: make(chan struct{}), - trigger: make(chan chan struct{}), - }, nil -} - -// Close closes the chain freezer instance and terminates the background thread. -func (f *chainThrower) Close() error { - select { - case <-f.quit: - default: - close(f.quit) - } - f.wg.Wait() - return nil -} - -// freeze is a background thread that periodically checks the blockchain for any -// import progress and moves ancient data from the fast database into the freezer. -// -// This functionality is deliberately broken off from block importing to avoid -// incurring additional data shuffling delays on block propagation. -func (f *chainThrower) throw(db ethdb.KeyValueStore) { - nfdb := &throwdb{KeyValueStore: db} - - var ( - backoff bool - triggered chan struct{} // Used in tests - ) - for { - select { - case <-f.quit: - log.Info("Thrower shutting down") - return - default: - } - if backoff { - // If we were doing a manual trigger, notify it - if triggered != nil { - triggered <- struct{}{} - triggered = nil - } - select { - case <-time.NewTimer(throwerRecheckInterval).C: - backoff = false - case triggered = <-f.trigger: - backoff = false - case <-f.quit: - return - } - } - // Retrieve the freezing threshold. - hash := ReadHeadBlockHash(nfdb) - if hash == (common.Hash{}) { - log.Debug("Current full block hash unavailable") // new chain, empty database - backoff = true - continue - } - number := ReadHeaderNumber(nfdb, hash) - threshold := atomic.LoadUint64(&f.threshold) - last := ReadAncientThrowLastBlockNumber(db) - if last == nil { - last = new(uint64) - } - switch { - case number == nil: - log.Error("Current full block number unavailable", "hash", hash) - backoff = true - continue - - case *number < threshold: - log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) - backoff = true - continue - - case *number-threshold <= *last: - log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "last", last) - backoff = true - continue - } - - storedSections := ReadStoredBloomSections(nfdb) - if storedSections*params.BloomBitsBlocks-1 < *last { - log.Warn("Attempt to prune the ancient blocks that bloom filter haven't finished yet, postpone to next round", "storedSections", storedSections, "pruneTo", *last) - return - } - - head := ReadHeader(nfdb, hash, *number) - if head == nil { - log.Error("Current full block unavailable", "number", *number, "hash", hash) - backoff = true - continue - } - - // Seems we have data ready to be frozen, process in usable batches - var ( - start = time.Now() - first = *last - limit = *number - threshold - ) - if limit-first > freezerBatchLimit { - limit = first + freezerBatchLimit - } - log.Info("schedule throwing blocks", "from", first, "to", limit) - ancients, err := f.throwRange(nfdb, first, limit) - if err != nil { - log.Error("Error in block freeze operation", "err", err) - backoff = true - continue - } - - // Wipe out all data from the active database - batch := db.NewBatch() - for i := 0; i < len(ancients); i++ { - // Always keep the genesis block in active database - if first+uint64(i) != 0 { - DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) - DeleteCanonicalHash(batch, first+uint64(i)) - } - } - if err := batch.Write(); err != nil { - log.Crit("Failed to throw ancient blocks", "err", err) - } - batch.Reset() - - // record - WriteAncientThrowLastBlockNumber(db, limit) - - // Wipe out side chains also and track dangling side chains - var dangling []common.Hash - for number := first; number < limit; number++ { - // Always keep the genesis block in active database - if number != 0 { - dangling = ReadAllHashes(db, number) - for _, hash := range dangling { - log.Trace("Deleting side chain", "number", number, "hash", hash) - DeleteBlock(batch, hash, number) - } - } - } - if err := batch.Write(); err != nil { - log.Crit("Failed to delete frozen side blocks", "err", err) - } - batch.Reset() - - // Step into the future and delete and dangling side chains - if limit > 0 { - tip := limit - for len(dangling) > 0 { - drop := make(map[common.Hash]struct{}) - for _, hash := range dangling { - log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) - drop[hash] = struct{}{} - } - children := ReadAllHashes(db, tip) - for i := 0; i < len(children); i++ { - // Dig up the child and ensure it's dangling - child := ReadHeader(nfdb, children[i], tip) - if child == nil { - log.Error("Missing dangling header", "number", tip, "hash", children[i]) - continue - } - if _, ok := drop[child.ParentHash]; !ok { - children = append(children[:i], children[i+1:]...) - i-- - continue - } - // Delete all block data associated with the child - log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) - DeleteBlock(batch, children[i], tip) - } - dangling = children - tip++ - } - if err := batch.Write(); err != nil { - log.Crit("Failed to delete dangling side blocks", "err", err) - } - } - - // Log something friendly for the user - context := []interface{}{ - "blocks", limit - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", limit - 1, - } - if n := len(ancients); n > 0 { - context = append(context, []interface{}{"hash", ancients[n-1]}...) - } - log.Info("Deep throw chain segment", context...) - - // Avoid database thrashing with tiny writes - if limit-first < freezerBatchLimit { - backoff = true - } - } -} - -func (f *chainThrower) throwRange(nfdb *throwdb, number, limit uint64) (hashes []common.Hash, err error) { - hashes = make([]common.Hash, 0, limit-number) - - for ; number <= limit; number++ { - // Retrieve all the components of the canonical block. - hash := ReadCanonicalHash(nfdb, number) - if hash == (common.Hash{}) { - log.Error("canonical hash missing, can't freeze", "block %d", number) - continue - } - header := ReadHeaderRLP(nfdb, hash, number) - if len(header) == 0 { - log.Error("block header missing, can't freeze", "block %d", number) - continue - } - body := ReadBodyRLP(nfdb, hash, number) - if len(body) == 0 { - log.Error("block body missing, can't freeze", "block %d", number) - continue - } - receipts := ReadReceiptsRLP(nfdb, hash, number) - if len(receipts) == 0 { - log.Error("block receipts missing, can't freeze", "block %d", number) - continue - } - td := ReadTdRLP(nfdb, hash, number) - if len(td) == 0 { - log.Error("total difficulty missing, can't freeze", "block %d", number) - continue - } - - hashes = append(hashes, hash) - } - - return hashes, err -} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 4960e439cd..adf9d0f2f0 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -165,7 +165,7 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { // NewDatabaseWithFreezer creates a high level database on top of a given key- // value data store with a freezer moving immutable chain segments into cold // storage. -func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, discardAncient bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newChainFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy) if err != nil { @@ -237,7 +237,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } } // Freezer is consistent with the key-value database, permit combining the two - if !frdb.readonly { + if !frdb.readonly && !discardAncient { frdb.wg.Add(1) go func() { frdb.freeze(db) @@ -250,29 +250,6 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }, nil } -// NewDatabaseWithThrower creates a high level database on top of a given key- -// value data store with a freezer moving immutable chain segments into cold -// storage. -func NewDatabaseWithThrower(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { - // Create the idle freezer instance - throwdb, err := newChainThrower(freezer, namespace, readonly) - if err != nil { - return nil, err - } - - // Thrower is consistent with the key-value database, permit combining the two - throwdb.wg.Add(1) - go func() { - throwdb.throw(db) - throwdb.wg.Done() - }() - - return &freezerdb{ - KeyValueStore: db, - AncientStore: throwdb, - }, nil -} - // NewMemoryDatabase creates an ephemeral in-memory key-value database without a // freezer moving immutable chain segments into cold storage. func NewMemoryDatabase() ethdb.Database { @@ -298,29 +275,13 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a // freezer moving immutable chain segments into cold storage. -func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool, ancientRecentLimit uint64) (ethdb.Database, error) { kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly) - if err != nil { - kvdb.Close() - return nil, err - } - return frdb, nil -} - -// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a -// freezer moving immutable chain segments into cold storage. -func NewLevelDBDatabaseWithThrower(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { - kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) - if err != nil { - return nil, err - } - - frdb, err := NewDatabaseWithThrower(kvdb, freezer, namespace, readonly) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, ancientRecentLimit != 0) if err != nil { kvdb.Close() return nil, err diff --git a/eth/backend.go b/eth/backend.go index 9a887ef2f0..4016ce5848 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -133,7 +133,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { ethashConfig.NotifyFull = config.Miner.NotifyFull // Assemble the Ethereum object - chainDb, err := stack.OpenDatabaseWrapper("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientRecentLimit != 0) + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientRecentLimit) if err != nil { return nil, err } @@ -215,7 +215,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.blockchain.SetHead(compat.RewindTo) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - eth.bloomIndexer.Start(eth.blockchain) + if config.AncientRecentLimit == 0 { + eth.bloomIndexer.Start(eth.blockchain) + } if config.TxPool.Journal != "" { config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) diff --git a/node/node.go b/node/node.go index b390abdb4b..c67bbd01d8 100644 --- a/node/node.go +++ b/node/node.go @@ -712,38 +712,6 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r return db, err } -// OpenDatabaseWrapper ... -func (n *Node) OpenDatabaseWrapper(name string, cache, handles int, freezer, namespace string, readonly, throwAncient bool) (ethdb.Database, error) { - n.lock.Lock() - defer n.lock.Unlock() - if n.state == closedState { - return nil, ErrNodeStopped - } - - var db ethdb.Database - var err error - if n.config.DataDir == "" { - db = rawdb.NewMemoryDatabase() - } else if !throwAncient { - root := n.ResolvePath(name) - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = n.ResolvePath(freezer) - } - db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) - } else { - root := n.ResolvePath(name) - db, err = rawdb.NewLevelDBDatabaseWithThrower(root, cache, handles, freezer, namespace, readonly) - } - - if err == nil { - db = n.wrapDatabase(db) - } - return db, err -} - // OpenDatabaseWithFreezer opens an existing database with the given name (or // creates one if no previous can be found) from within the node's data directory, // also attaching a chain freezer to it that moves ancient chain data from the @@ -768,7 +736,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, case !filepath.IsAbs(freezer): freezer = n.ResolvePath(freezer) } - db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, ancientRecentLimit) } if err == nil { From d46ea0ebf7f30d5f3ec81a40cd52de04520a5d80 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 30 Jun 2022 22:57:15 +0800 Subject: [PATCH 4/8] remove unneeded code --- core/rawdb/accessors_chain.go | 18 ------------------ core/rawdb/schema.go | 3 --- 2 files changed, 21 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 4fbba22b55..8ea2e2ca72 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -168,24 +168,6 @@ func DeleteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash) { } } -// ReadAncientThrowLastBlock retrieves the hash of the finalized block. -func ReadAncientThrowLastBlockNumber(db ethdb.KeyValueReader) *uint64 { - data, _ := db.Get(ancientThrowLastBlock) - if len(data) != 8 { - return nil - } - number := binary.BigEndian.Uint64(data) - return &number -} - -// WriteAncientThrowLastBlock stores the hash of the finalized block. -func WriteAncientThrowLastBlockNumber(db ethdb.KeyValueWriter, number uint64) { - enc := encodeBlockNumber(number) - if err := db.Put(ancientThrowLastBlock, enc); err != nil { - log.Crit("Failed to store last throwed ancient block's hash", "err", err) - } -} - // ReadHeadHeaderHash retrieves the hash of the current canonical head header. func ReadHeadHeaderHash(db ethdb.KeyValueReader) common.Hash { data, _ := db.Get(headHeaderKey) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dce29a2d14..041c9f0449 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -84,9 +84,6 @@ var ( // transitionStatusKey tracks the eth2 transition status. transitionStatusKey = []byte("eth2-transition") - // ancientThrowLastBlock tracks the last recent ancient block that has been thrown - ancientThrowLastBlock = []byte("AncientThrowLastBlock") - // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td From 815f402a56caa03780d2644d1227941f6e8775f7 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 1 Jul 2022 09:28:57 +0800 Subject: [PATCH 5/8] refine the implementation --- cmd/geth/chaincmd.go | 2 +- cmd/utils/flags.go | 19 +++++++++---------- core/blockchain.go | 7 +++---- core/rawdb/database.go | 8 ++++---- eth/backend.go | 6 +++--- eth/ethconfig/config.go | 2 +- node/node.go | 4 ++-- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 0b09344bf0..54271e11fd 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -192,7 +192,7 @@ func initGenesis(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { - chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, ctx.GlobalString(utils.AncientFlag.Name), "", false, ctx.GlobalUint64(utils.AncientRecentLimitFlag.Name)) + chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, ctx.GlobalString(utils.AncientFlag.Name), "", false, ctx.GlobalBool(utils.AncientPruneFlag.Name)) if err != nil { utils.Fatalf("Failed to open database: %v", err) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c80de61104..a0363ee8fd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -122,10 +122,9 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } - AncientRecentLimitFlag = cli.Uint64Flag{ - Name: "ancient.recentlimit", - Usage: "Keep only the specified amount of recent ancient blocks and won't do ancient related checks on startup (default = 0, means keep all)", - Value: 0, + AncientPruneFlag = cli.BoolFlag{ + Name: "ancient.prune", + Usage: "Totally discard the ancient blocks instead of writting them to the freezer db", } MinFreeDiskSpaceFlag = DirectoryFlag{ Name: "datadir.minfreedisk", @@ -854,7 +853,7 @@ var ( DatabasePathFlags = []cli.Flag{ DataDirFlag, AncientFlag, - AncientRecentLimitFlag, + AncientPruneFlag, RemoteDBFlag, } ) @@ -1595,8 +1594,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { ctx.GlobalSet(TxLookupLimitFlag.Name, "0") log.Warn("Disable transaction unindexing for archive node") } - if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(AncientRecentLimitFlag.Name) != 0 { - ctx.GlobalSet(AncientRecentLimitFlag.Name, "0") + if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(AncientPruneFlag.Name) != 0 { + ctx.GlobalSet(AncientPruneFlag.Name, "false") log.Warn("Disable ancient prunning for archive node") } if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { @@ -1665,8 +1664,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(TxLookupLimitFlag.Name) { cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name) } - if ctx.GlobalIsSet(AncientRecentLimitFlag.Name) { - cfg.AncientRecentLimit = ctx.GlobalUint64(AncientRecentLimitFlag.Name) + if ctx.GlobalIsSet(AncientPruneFlag.Name) { + cfg.AncientPrune = ctx.GlobalBool(AncientPruneFlag.Name) } if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100 @@ -1991,7 +1990,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. case ctx.GlobalString(SyncModeFlag.Name) == "light": chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly) default: - chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, ctx.GlobalUint64(AncientRecentLimitFlag.Name)) + chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, ctx.GlobalBool(AncientPruneFlag.Name)) } if err != nil { Fatalf("Could not open database: %v", err) diff --git a/core/blockchain.go b/core/blockchain.go index 92331c6bea..3d38b5f9f3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -140,8 +140,7 @@ type CacheConfig struct { SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory Preimages bool // Whether to store preimage of trie key to the disk - AncientRecentLimit uint64 - + AncientPrune bool SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } @@ -406,7 +405,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par // Start tx indexer/unindexer. if txLookupLimit != nil { bc.txLookupLimit = *txLookupLimit - if bc.cacheConfig.AncientRecentLimit != 0 { + if bc.cacheConfig.AncientPrune { bc.txLookupLimit = params.FullImmutabilityThreshold } @@ -940,7 +939,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ bc.wg.Add(1) defer bc.wg.Done() - if bc.cacheConfig.AncientRecentLimit != 0 { + if bc.cacheConfig.AncientPrune { ancientLimit = 0 } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index adf9d0f2f0..e660189f8b 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -165,7 +165,7 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { // NewDatabaseWithFreezer creates a high level database on top of a given key- // value data store with a freezer moving immutable chain segments into cold // storage. -func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, discardAncient bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, ancientPrune bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newChainFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy) if err != nil { @@ -237,7 +237,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } } // Freezer is consistent with the key-value database, permit combining the two - if !frdb.readonly && !discardAncient { + if !frdb.readonly && !ancientPrune { frdb.wg.Add(1) go func() { frdb.freeze(db) @@ -275,13 +275,13 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a // freezer moving immutable chain segments into cold storage. -func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool, ancientRecentLimit uint64) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly, ancientPrune bool) (ethdb.Database, error) { kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, ancientRecentLimit != 0) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, ancientPrune) if err != nil { kvdb.Close() return nil, err diff --git a/eth/backend.go b/eth/backend.go index 4016ce5848..3ed4114b25 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -133,7 +133,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { ethashConfig.NotifyFull = config.Miner.NotifyFull // Assemble the Ethereum object - chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientRecentLimit) + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false, config.AncientPrune) if err != nil { return nil, err } @@ -202,7 +202,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, Preimages: config.Preimages, - AncientRecentLimit: config.AncientRecentLimit, + AncientPrune: config.AncientPrune, } ) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) @@ -215,7 +215,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.blockchain.SetHead(compat.RewindTo) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - if config.AncientRecentLimit == 0 { + if !config.AncientPrune { eth.bloomIndexer.Start(eth.blockchain) } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index c37faa1688..0da200eca8 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -163,7 +163,7 @@ type Config struct { DatabaseHandles int `toml:"-"` DatabaseCache int DatabaseFreezer string - AncientRecentLimit uint64 + AncientPrune bool TrieCleanCache int TrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts diff --git a/node/node.go b/node/node.go index c67bbd01d8..a3f559588e 100644 --- a/node/node.go +++ b/node/node.go @@ -717,7 +717,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r // also attaching a chain freezer to it that moves ancient chain data from the // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. -func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly bool, ancientRecentLimit uint64) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly, ancientPrune bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -736,7 +736,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, case !filepath.IsAbs(freezer): freezer = n.ResolvePath(freezer) } - db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, ancientRecentLimit) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, ancientPrune) } if err == nil { From a6e42641a2c08330a16337f566e1ffd82422b444 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 1 Jul 2022 10:58:04 +0800 Subject: [PATCH 6/8] skip the ancient check when prunning turned on --- core/rawdb/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index e660189f8b..bd7683babc 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -194,7 +194,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // validate in this method. If, however, the genesis hash is not nil, compare // it to the freezer content. // TODO consider deprecate this check - if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { + if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 && !ancientPrune { if frozen, _ := frdb.Ancients(); frozen > 0 { // If the freezer already contains something, ensure that the genesis blocks // match, otherwise we might mix up freezers across chains and destroy both From 0a0a8733ab0599137be3775493d4c5db44ef65d0 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 1 Jul 2022 11:19:26 +0800 Subject: [PATCH 7/8] fix grammer error raised by golint-ci --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a0363ee8fd..c0220258ca 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -124,7 +124,7 @@ var ( } AncientPruneFlag = cli.BoolFlag{ Name: "ancient.prune", - Usage: "Totally discard the ancient blocks instead of writting them to the freezer db", + Usage: "Totally discard the ancient blocks instead of writing them to the freezer db", } MinFreeDiskSpaceFlag = DirectoryFlag{ Name: "datadir.minfreedisk", From 46e99e460b25494b7eb34598d3ccf5287a2e755a Mon Sep 17 00:00:00 2001 From: user Date: Fri, 1 Jul 2022 12:27:06 +0800 Subject: [PATCH 8/8] add flag for controlling whether blocks should be pruned during undexing transactions --- core/blockchain.go | 4 ++-- core/rawdb/chain_iterator.go | 10 +++++----- core/rawdb/chain_iterator_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 3d38b5f9f3..753a9602df 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2302,7 +2302,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } else { // Prune all stale tx indices and record the tx index tail log.Info("Scheduled blocks & transactions unindexing", "from block", 0, "to", head-bc.txLookupLimit+1) - rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit) + rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit, bc.cacheConfig.AncientPrune) } return } @@ -2327,7 +2327,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } else { // Unindex a part of stale indices and forward index tail to HEAD-limit log.Info("Scheduled blocks & transactions unindexing", "from block", *tail, "to", head-bc.txLookupLimit+1) - rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit) + rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit, bc.cacheConfig.AncientPrune) } } diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 4d4e42b9dc..04f10036d7 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -273,7 +273,7 @@ func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, inte // // There is a passed channel, the whole procedure will be interrupted if any // signal received. -func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { +func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, unindexBlock bool) { // short circuit for invalid range if from >= to { return @@ -308,7 +308,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch DeleteTxLookupEntries(batch, delivery.hashes) txs += len(delivery.hashes) // Delete all about the block - if delivery.number != 0 { // never delete the genesis block + if unindexBlock && delivery.number != 0 { // never delete the genesis block DeleteBlock(batch, delivery.blockHash, delivery.number) } blocks++ @@ -352,11 +352,11 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch // // There is a passed channel, the whole procedure will be interrupted if any // signal received. -func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) { - unindexTransactions(db, from, to, interrupt, nil) +func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, unindexBlock bool) { + unindexTransactions(db, from, to, interrupt, nil, unindexBlock) } // unindexTransactionsForTesting is the internal debug version with an additional hook. func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { - unindexTransactions(db, from, to, interrupt, hook) + unindexTransactions(db, from, to, interrupt, hook, false) } diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index e1f5159753..15d94598fa 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -169,11 +169,11 @@ func TestIndexTransactions(t *testing.T) { IndexTransactions(chainDb, 0, 5, nil) verify(0, 11, true, 0) - UnindexTransactions(chainDb, 0, 5, nil) + UnindexTransactions(chainDb, 0, 5, nil, false) verify(5, 11, true, 5) verify(0, 5, false, 5) - UnindexTransactions(chainDb, 5, 11, nil) + UnindexTransactions(chainDb, 5, 11, nil, false) verify(0, 11, false, 11) // Testing corner cases