From e42f4e32f40bc14c48750d89cc98f2de1532f0da Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Sun, 14 Nov 2021 17:52:24 +0800 Subject: [PATCH 01/26] offline block prune --- cmd/geth/snapshot.go | 91 ++++++++++++++++++++++++ cmd/utils/flags.go | 4 ++ core/rawdb/accessors_chain.go | 1 + core/rawdb/freezer.go | 47 +++++++++++++ core/state/pruner/pruner.go | 129 ++++++++++++++++++++++++++++++++++ ethdb/database.go | 4 ++ 6 files changed, 276 insertions(+) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 2f615a2c18..1502f2b0a7 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -18,11 +18,15 @@ package main import ( "bytes" + "encoding/json" "errors" + "os" + "path/filepath" "time" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/pruner" @@ -78,6 +82,36 @@ WARNING: It's necessary to delete the trie clean cache after the pruning. If you specify another directory for the trie clean cache via "--cache.trie.journal" during the use of Geth, please also specify it here for correct deletion. Otherwise the trie clean cache with default directory will be deleted. +`, + }, + { + Name: "prune-block-pre-backup", + Usage: "Back up the ancient block data", + ArgsUsage: "", + Action: utils.MigrateFlags(pruneBlockPreBackUp), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.AncientBackUpFlag, + }, + Description: ` +Back up the ancient block data offline before prune block started. +`, + }, + { + Name: "prune-block", + Usage: "Prune block data offline", + ArgsUsage: "", + Action: utils.MigrateFlags(pruneBlock), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.AncientBackUpFlag, + }, + Description: ` +Offline prune for block data. `, }, { @@ -149,6 +183,63 @@ It's also usable without snapshot enabled. } ) +func pruneBlockPreBackUp(ctx *cli.Context) error { + // Make sure we have a valid genesis JSON + genesisPath := ctx.Args().First() + if len(genesisPath) == 0 { + utils.Fatalf("Must supply path to genesis JSON file") + } + file, err := os.Open(genesisPath) + if err != nil { + utils.Fatalf("Failed to read genesis file: %v", err) + } + defer file.Close() + + genesis := new(core.Genesis) + if err := json.NewDecoder(file).Decode(genesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + + stack, config := makeConfigNode(ctx) + defer stack.Close() + freezer := config.Eth.DatabaseFreezer + chaindb := utils.MakeChainDatabase(ctx, stack, false) + if err != nil { + utils.Fatalf("Failed to open ancient database: %v", err) + } + + for _, name := range []string{"chaindata"} { + root := stack.ResolvePath(name) // /Users/user/storage/Private_BSC_Storage/build/bin/node/geth/chaindata + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = stack.ResolvePath(freezer) + } + pruner, err := pruner.NewBlockPruner(chaindb, stack, stack.ResolvePath(""), freezer, genesis) + if err != nil { + utils.Fatalf("Failed to create block pruner", err) + } + backfreezer := filepath.Join(root, "ancient_back_up") + if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, config.Eth.DatabaseHandles, backfreezer, "eth/db/chaindata/", false); err != nil { + log.Error("Failed to back up block", "err", err) + return err + } + } + log.Info("geth block offline pruning backup successfully") + return nil +} + +func pruneBlock(ctx *cli.Context) error { + oldAncientPath := ctx.GlobalString(utils.AncientFlag.Name) + newAncientPath := ctx.GlobalString(utils.AncientBackUpFlag.Name) + if err := pruner.BlockPrune(oldAncientPath, newAncientPath); err != nil { + utils.Fatalf("Failed to prune block", err) + return err + } + return nil +} + func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 67632c031b..c3f30048f8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -134,6 +134,10 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } + AncientBackUpFlag = DirectoryFlag{ + Name: "datadir.ancient", + Usage: "Data directory for ancient directory backup (default = inside chaindata)", + } DiffFlag = DirectoryFlag{ Name: "datadir.diff", Usage: "Data directory for difflayer segments (default = inside chaindata)", diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 6489a600fb..2e9d95b2e2 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -724,6 +724,7 @@ func WriteAncientBlock(db ethdb.AncientWriter, block *types.Block, receipts type return len(headerBlob) + len(bodyBlob) + len(receiptBlob) + len(tdBlob) + common.HashLength } + // DeleteBlock removes all block data associated with a hash. func DeleteBlock(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { DeleteReceipts(db, hash, number) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 94b99a64eb..30b8db64de 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -240,6 +240,53 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td return nil } +// AppendAncient injects all binary blobs except for block body at the end of the +// append-only immutable table files. +// +// Notably, this function is lock free but kind of thread-safe. All out-of-order +// injection will be rejected. But if two injections with same number happen at +// the same time, we can get into the trouble. +func (f *freezer) AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) (err error) { + if f.readonly { + return errReadOnly + } + // Ensure the binary blobs we are appending is continuous with freezer. + if atomic.LoadUint64(&f.frozen) != number { + return errOutOrderInsertion + } + // Rollback all inserted data if any insertion below failed to ensure + // the tables won't out of sync. + defer func() { + if err != nil { + rerr := f.repair() + if rerr != nil { + log.Crit("Failed to repair freezer", "err", rerr) + } + log.Info("Append ancient failed", "number", number, "err", err) + } + }() + // Inject all the components into the relevant data tables + if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { + log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) + return err + } + if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { + log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) + return err + } + + if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { + log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) + return err + } + if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { + log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) + return err + } + atomic.AddUint64(&f.frozen, 1) // Only modify atomically + return nil +} + // TruncateAncients discards any recent data above the provided threshold number. func (f *freezer) TruncateAncients(items uint64) error { if f.readonly { diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index bb599fcd87..7469e3c303 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -22,12 +22,15 @@ import ( "errors" "fmt" "math" + "math/big" "os" "path/filepath" "strings" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -35,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -85,6 +89,15 @@ type Pruner struct { triesInMemory uint64 } +type BlockPruner struct { + db ethdb.Database + chaindbDir string + ancientdbDir string + headHeader *types.Header + n *node.Node + genesis *core.Genesis +} + // NewPruner creates the pruner instance. func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, triesInMemory uint64) (*Pruner, error) { headBlock := rawdb.ReadHeadBlock(db) @@ -115,6 +128,17 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } +func NewBlockPruner(db ethdb.Database, n *node.Node, chaindbDir, ancientdbDir string, genesis *core.Genesis) (*BlockPruner, error) { + + return &BlockPruner{ + db: db, + chaindbDir: chaindbDir, + ancientdbDir: ancientdbDir, + n: n, + genesis: genesis, + }, nil +} + func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error { // Delete all stale trie nodes in the disk. With the help of state bloom // the trie nodes(and codes) belong to the active state will be filtered @@ -233,6 +257,111 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } +// Prune block body data +func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFreezer, namespace string, readonly bool) error { + //Back-up the necessary data within original ancient directory, create new freezer backup directory backFreezer + //db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, backFreezer, namespace, readonly) + start := time.Now() + chainDb := p.db + chainDbBack, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, backFreezer, namespace, readonly) + if err != nil { + log.Error("Failed to open ancient database: %v", err) + return err + } + + //write back-up data to new chainDb + // Restore the last known head block + + //write genesis block firstly + genesis := p.genesis + if _, _, err := core.SetupGenesisBlock(chainDbBack, genesis); err != nil { + log.Error("Failed to write genesis block: %v", err) + return err + } + + //write most recent 128 blocks data + headBlock := rawdb.ReadHeadBlock(chainDb) + if headBlock == nil { + return errors.New("Failed to load head block") + } + lastBlockNumber := headBlock.NumberU64() + + //For block number 1 to current block-128, only back-up receipts, difficulties, block number->hash but no body data anymore + for blockNumber := lastBlockNumber - 128; blockNumber >= 1; blockNumber-- { + blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) + block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) + receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) + // Calculate the total difficulty of the block + td := rawdb.ReadTd(chainDb, blockHash, blockNumber) + if td == nil { + return consensus.ErrUnknownAncestor + } + externTd := new(big.Int).Add(block.Difficulty(), td) + // Encode all block components to RLP format. + headerBlob, err := rlp.EncodeToBytes(block.Header()) + if err != nil { + log.Crit("Failed to RLP encode block header", "err", err) + } + + storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) + for i, receipt := range receipts { + storageReceipts[i] = (*types.ReceiptForStorage)(receipt) + } + receiptBlob, err := rlp.EncodeToBytes(storageReceipts) + if err != nil { + log.Crit("Failed to RLP encode block receipts", "err", err) + } + tdBlob, err := rlp.EncodeToBytes(externTd) + if err != nil { + log.Crit("Failed to RLP encode block total difficulty", "err", err) + } + // Write all blob to flatten files. + err = chainDbBack.AppendAncientNoBody(block.NumberU64(), block.Hash().Bytes(), headerBlob, receiptBlob, tdBlob) + if err != nil { + log.Crit("Failed to write block data to ancient store", "err", err) + } + + return nil + } + + //All ancient data within the most recent 128 blocks write into new ancient_back directory + for blockNumber := lastBlockNumber - 127; blockNumber <= lastBlockNumber; blockNumber++ { + blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) + block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) + receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) + // Calculate the total difficulty of the block + td := rawdb.ReadTd(chainDb, blockHash, blockNumber) + if td == nil { + return consensus.ErrUnknownAncestor + } + externTd := new(big.Int).Add(block.Difficulty(), td) + rawdb.WriteAncientBlock(chainDbBack, block, receipts, externTd) + } + + chainDb.Close() + chainDbBack.Close() + + log.Info("Block pruning BackUp successful", common.PrettyDuration(time.Since(start))) + return nil +} + +func BlockPrune(oldAncientPath, newAncientPath string) error { + //Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient + if err := os.RemoveAll(oldAncientPath); err != nil { + log.Error("Failed to remove old ancient directory %v", err) + + return err + } + + //Rename the new ancientdb path same to the old + if err := os.Rename(newAncientPath, oldAncientPath); err != nil { + log.Error("Failed to rename new ancient directory %v", err) + return err + } + return nil + +} + // Prune deletes all historical state nodes except the nodes belong to the // specified state version. If user doesn't specify the state version, use // the bottom-most snapshot diff layer as the target. diff --git a/ethdb/database.go b/ethdb/database.go index 40d0e01d57..af97342938 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -94,6 +94,10 @@ type AncientWriter interface { // Sync flushes all in-memory ancient store data to disk. Sync() error + + // AppendAncient injects all binary blobs except for block body at the end of the + // append-only immutable table files. + AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) error } // Reader contains the methods required to read data from both key-value as well as From 92718905594220eca09ffa14e879a8d844c9964f Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 7 Dec 2021 18:24:05 +0800 Subject: [PATCH 02/26] update --- cmd/geth/main.go | 2 ++ cmd/geth/snapshot.go | 37 ++++++----------------- cmd/utils/flags.go | 8 +++-- core/rawdb/database.go | 5 ++++ core/rawdb/freezer.go | 47 ----------------------------- core/state/pruner/pruner.go | 59 +++++++------------------------------ ethdb/database.go | 3 -- 7 files changed, 33 insertions(+), 128 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3e2585fed6..a2d8db0547 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -165,6 +165,8 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, + utils.AncientBackUpFlag, + utils.GenesisFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 1502f2b0a7..04cb64c35a 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -82,21 +82,6 @@ WARNING: It's necessary to delete the trie clean cache after the pruning. If you specify another directory for the trie clean cache via "--cache.trie.journal" during the use of Geth, please also specify it here for correct deletion. Otherwise the trie clean cache with default directory will be deleted. -`, - }, - { - Name: "prune-block-pre-backup", - Usage: "Back up the ancient block data", - ArgsUsage: "", - Action: utils.MigrateFlags(pruneBlockPreBackUp), - Category: "MISCELLANEOUS COMMANDS", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.AncientFlag, - utils.AncientBackUpFlag, - }, - Description: ` -Back up the ancient block data offline before prune block started. `, }, { @@ -109,6 +94,7 @@ Back up the ancient block data offline before prune block started. utils.DataDirFlag, utils.AncientFlag, utils.AncientBackUpFlag, + utils.GenesisFlag, }, Description: ` Offline prune for block data. @@ -183,9 +169,12 @@ It's also usable without snapshot enabled. } ) -func pruneBlockPreBackUp(ctx *cli.Context) error { +func pruneBlock(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + //defer stack.Close() + chaindb := utils.MakeChainDatabase(ctx, stack, false) // Make sure we have a valid genesis JSON - genesisPath := ctx.Args().First() + genesisPath := ctx.GlobalString(utils.GenesisFlag.Name) if len(genesisPath) == 0 { utils.Fatalf("Must supply path to genesis JSON file") } @@ -199,14 +188,10 @@ func pruneBlockPreBackUp(ctx *cli.Context) error { if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } - - stack, config := makeConfigNode(ctx) - defer stack.Close() - freezer := config.Eth.DatabaseFreezer - chaindb := utils.MakeChainDatabase(ctx, stack, false) if err != nil { - utils.Fatalf("Failed to open ancient database: %v", err) + utils.Fatalf("Failed to decode genesis: %v", err) } + freezer := config.Eth.DatabaseFreezer for _, name := range []string{"chaindata"} { root := stack.ResolvePath(name) // /Users/user/storage/Private_BSC_Storage/build/bin/node/geth/chaindata @@ -221,16 +206,12 @@ func pruneBlockPreBackUp(ctx *cli.Context) error { utils.Fatalf("Failed to create block pruner", err) } backfreezer := filepath.Join(root, "ancient_back_up") - if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, config.Eth.DatabaseHandles, backfreezer, "eth/db/chaindata/", false); err != nil { + if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), backfreezer, "", false); err != nil { log.Error("Failed to back up block", "err", err) return err } } log.Info("geth block offline pruning backup successfully") - return nil -} - -func pruneBlock(ctx *cli.Context) error { oldAncientPath := ctx.GlobalString(utils.AncientFlag.Name) newAncientPath := ctx.GlobalString(utils.AncientBackUpFlag.Name) if err := pruner.BlockPrune(oldAncientPath, newAncientPath); err != nil { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c3f30048f8..b74e7e3a5f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -135,8 +135,12 @@ var ( Usage: "Data directory for ancient chain segments (default = inside chaindata)", } AncientBackUpFlag = DirectoryFlag{ - Name: "datadir.ancient", - Usage: "Data directory for ancient directory backup (default = inside chaindata)", + Name: "datadir.backup", + Usage: "Data directory for ancient directory backup", + } + GenesisFlag = DirectoryFlag{ + Name: "datadir.genesis", + Usage: "Data directory for genesis file", } DiffFlag = DirectoryFlag{ Name: "datadir.diff", diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 82d5df06ce..a3cc66231f 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -122,6 +122,11 @@ func (db *nofreezedb) AppendAncient(number uint64, hash, header, body, receipts, return errNotSupported } +// AppendAncientNoBody returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) error { + return errNotSupported +} + // TruncateAncients returns an error as we don't have a backing chain freezer. func (db *nofreezedb) TruncateAncients(items uint64) error { return errNotSupported diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 30b8db64de..94b99a64eb 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -240,53 +240,6 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td return nil } -// AppendAncient injects all binary blobs except for block body at the end of the -// append-only immutable table files. -// -// Notably, this function is lock free but kind of thread-safe. All out-of-order -// injection will be rejected. But if two injections with same number happen at -// the same time, we can get into the trouble. -func (f *freezer) AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) (err error) { - if f.readonly { - return errReadOnly - } - // Ensure the binary blobs we are appending is continuous with freezer. - if atomic.LoadUint64(&f.frozen) != number { - return errOutOrderInsertion - } - // Rollback all inserted data if any insertion below failed to ensure - // the tables won't out of sync. - defer func() { - if err != nil { - rerr := f.repair() - if rerr != nil { - log.Crit("Failed to repair freezer", "err", rerr) - } - log.Info("Append ancient failed", "number", number, "err", err) - } - }() - // Inject all the components into the relevant data tables - if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { - log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) - return err - } - if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { - log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) - return err - } - - if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { - log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) - return err - } - if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { - log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) - return err - } - atomic.AddUint64(&f.frozen, 1) // Only modify atomically - return nil -} - // TruncateAncients discards any recent data above the provided threshold number. func (f *freezer) TruncateAncients(items uint64) error { if f.readonly { diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 7469e3c303..437eec52a6 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -262,7 +262,6 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree //Back-up the necessary data within original ancient directory, create new freezer backup directory backFreezer //db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, backFreezer, namespace, readonly) start := time.Now() - chainDb := p.db chainDbBack, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, backFreezer, namespace, readonly) if err != nil { log.Error("Failed to open ancient database: %v", err) @@ -270,8 +269,6 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree } //write back-up data to new chainDb - // Restore the last known head block - //write genesis block firstly genesis := p.genesis if _, _, err := core.SetupGenesisBlock(chainDbBack, genesis); err != nil { @@ -279,53 +276,19 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree return err } - //write most recent 128 blocks data - headBlock := rawdb.ReadHeadBlock(chainDb) - if headBlock == nil { - return errors.New("Failed to load head block") + //write the latest 128 blocks data of the ancient db + // If we can't access the freezer or it's empty, abort + frozen, err := p.db.Ancients() + if err != nil || frozen == 0 { + return errors.New("Can't access the freezer or it's empty, abort") } - lastBlockNumber := headBlock.NumberU64() - - //For block number 1 to current block-128, only back-up receipts, difficulties, block number->hash but no body data anymore - for blockNumber := lastBlockNumber - 128; blockNumber >= 1; blockNumber-- { - blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) - block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) - receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) - // Calculate the total difficulty of the block - td := rawdb.ReadTd(chainDb, blockHash, blockNumber) - if td == nil { - return consensus.ErrUnknownAncestor - } - externTd := new(big.Int).Add(block.Difficulty(), td) - // Encode all block components to RLP format. - headerBlob, err := rlp.EncodeToBytes(block.Header()) - if err != nil { - log.Crit("Failed to RLP encode block header", "err", err) - } - - storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) - for i, receipt := range receipts { - storageReceipts[i] = (*types.ReceiptForStorage)(receipt) - } - receiptBlob, err := rlp.EncodeToBytes(storageReceipts) - if err != nil { - log.Crit("Failed to RLP encode block receipts", "err", err) - } - tdBlob, err := rlp.EncodeToBytes(externTd) - if err != nil { - log.Crit("Failed to RLP encode block total difficulty", "err", err) - } - // Write all blob to flatten files. - err = chainDbBack.AppendAncientNoBody(block.NumberU64(), block.Hash().Bytes(), headerBlob, receiptBlob, tdBlob) - if err != nil { - log.Crit("Failed to write block data to ancient store", "err", err) - } - - return nil + start_index := frozen - 128 + if start_index < 0 { + start_index = 0 } - //All ancient data within the most recent 128 blocks write into new ancient_back directory - for blockNumber := lastBlockNumber - 127; blockNumber <= lastBlockNumber; blockNumber++ { + chainDb := p.db + for blockNumber := start_index; blockNumber < frozen; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) @@ -337,6 +300,7 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree externTd := new(big.Int).Add(block.Difficulty(), td) rawdb.WriteAncientBlock(chainDbBack, block, receipts, externTd) } + //chainDb.TruncateAncients(start_index - 1) chainDb.Close() chainDbBack.Close() @@ -359,7 +323,6 @@ func BlockPrune(oldAncientPath, newAncientPath string) error { return err } return nil - } // Prune deletes all historical state nodes except the nodes belong to the diff --git a/ethdb/database.go b/ethdb/database.go index af97342938..c399f45c20 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -95,9 +95,6 @@ type AncientWriter interface { // Sync flushes all in-memory ancient store data to disk. Sync() error - // AppendAncient injects all binary blobs except for block body at the end of the - // append-only immutable table files. - AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) error } // Reader contains the methods required to read data from both key-value as well as From d4d8324ae855bb720802987ec466217af250fc3f Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Fri, 10 Dec 2021 00:45:38 +0800 Subject: [PATCH 03/26] update --- cmd/geth/main.go | 2 - cmd/geth/snapshot.go | 69 ++++++++-------- cmd/utils/flags.go | 30 +++++-- core/rawdb/accessors_chain.go | 15 +++- core/rawdb/database.go | 149 ++++++++++++++++++++++++---------- core/rawdb/freezer.go | 13 ++- core/rawdb/schema.go | 1 + core/state/pruner/pruner.go | 70 ++++++++-------- ethdb/database.go | 1 - node/node.go | 66 +++++++++++++++ 10 files changed, 293 insertions(+), 123 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a2d8db0547..3e2585fed6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -165,8 +165,6 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, - utils.AncientBackUpFlag, - utils.GenesisFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 04cb64c35a..409d0ae67e 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -18,15 +18,12 @@ package main import ( "bytes" - "encoding/json" "errors" - "os" "path/filepath" "time" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/pruner" @@ -93,8 +90,6 @@ the trie clean cache with default directory will be deleted. Flags: []cli.Flag{ utils.DataDirFlag, utils.AncientFlag, - utils.AncientBackUpFlag, - utils.GenesisFlag, }, Description: ` Offline prune for block data. @@ -171,56 +166,62 @@ It's also usable without snapshot enabled. func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) - //defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, false) - // Make sure we have a valid genesis JSON - genesisPath := ctx.GlobalString(utils.GenesisFlag.Name) - if len(genesisPath) == 0 { - utils.Fatalf("Must supply path to genesis JSON file") - } - file, err := os.Open(genesisPath) - if err != nil { - utils.Fatalf("Failed to read genesis file: %v", err) - } - defer file.Close() + defer stack.Close() - genesis := new(core.Genesis) - if err := json.NewDecoder(file).Decode(genesis); err != nil { - utils.Fatalf("invalid genesis file: %v", err) - } - if err != nil { - utils.Fatalf("Failed to decode genesis: %v", err) + chaindb := utils.MakeChainDatabaseForBlockPrune(ctx, stack, false) + chaindb.Close() + + var oldAncientPath, newAncientPath string + if path := getAncientPath(ctx); path != "" { + oldAncientPath = path + "/ancient" + newAncientPath = path + "/ancient_back" + } else { + utils.Fatalf("Prune failed, did not specify the AncientPath %v") } - freezer := config.Eth.DatabaseFreezer + //lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "FLOCK")) + // lock, _, err := fileutil.Flock(oldAncientPath) + // if err != nil { + // return err + // } + // _, err = os.Open(oldAncientPath) + // if err != nil { + // utils.Fatalf("Failed to read genesis file: %v", err) + // } for _, name := range []string{"chaindata"} { - root := stack.ResolvePath(name) // /Users/user/storage/Private_BSC_Storage/build/bin/node/geth/chaindata + root := stack.ResolvePath(name) switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = stack.ResolvePath(freezer) + case oldAncientPath == "": + oldAncientPath = filepath.Join(root, "ancient") + case !filepath.IsAbs(oldAncientPath): + oldAncientPath = stack.ResolvePath(oldAncientPath) } - pruner, err := pruner.NewBlockPruner(chaindb, stack, stack.ResolvePath(""), freezer, genesis) + pruner, err := pruner.NewBlockPruner(chaindb, stack, stack.ResolvePath(""), oldAncientPath) if err != nil { utils.Fatalf("Failed to create block pruner", err) } - backfreezer := filepath.Join(root, "ancient_back_up") - if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), backfreezer, "", false); err != nil { + + if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), newAncientPath, oldAncientPath, "", false); err != nil { log.Error("Failed to back up block", "err", err) return err } } + log.Info("geth block offline pruning backup successfully") - oldAncientPath := ctx.GlobalString(utils.AncientFlag.Name) - newAncientPath := ctx.GlobalString(utils.AncientBackUpFlag.Name) + if err := pruner.BlockPrune(oldAncientPath, newAncientPath); err != nil { utils.Fatalf("Failed to prune block", err) return err } + //lock.Release() + log.Info("Block prune successfully") return nil } +func getAncientPath(ctx *cli.Context) string { + return ctx.GlobalString(utils.AncientFlag.Name) +} + func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b74e7e3a5f..202b463a1c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -134,14 +134,6 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } - AncientBackUpFlag = DirectoryFlag{ - Name: "datadir.backup", - Usage: "Data directory for ancient directory backup", - } - GenesisFlag = DirectoryFlag{ - Name: "datadir.genesis", - Usage: "Data directory for genesis file", - } DiffFlag = DirectoryFlag{ Name: "datadir.diff", Usage: "Data directory for difflayer segments (default = inside chaindata)", @@ -1912,6 +1904,28 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. return chainDb } +// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. +func MakeChainDatabaseForBlockPrune(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database { + var ( + cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 + handles = MakeDatabaseHandles() + + err error + chainDb ethdb.Database + ) + if ctx.GlobalString(SyncModeFlag.Name) == "light" { + name := "lightchaindata" + chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) + } else { + name := "chaindata" + chainDb, err = stack.OpenDatabaseWithFreezerForPruneBlock(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) + } + if err != nil { + Fatalf("Could not open database: %v", err) + } + return chainDb +} + func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 2e9d95b2e2..f52fb997ca 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -294,6 +294,20 @@ func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValu return nil // Can't find the data anywhere. } +func ReadOffSetOfAncientFreezer(db ethdb.KeyValueStore) uint64 { + offset, _ := db.Get(offSetOfAncientFreezer) + if offset == nil { + return 0 + } + return new(big.Int).SetBytes(offset).Uint64() +} + +func WriteOffSetOfAncientFreezer(db ethdb.KeyValueStore, offset uint64) { + if err := db.Put(offSetOfAncientFreezer, new(big.Int).SetUint64(offset).Bytes()); err != nil { + log.Crit("Failed to store offSetOfAncientFreezer", "err", err) + } +} + // HasHeader verifies the existence of a block header corresponding to the hash. func HasHeader(db ethdb.Reader, hash common.Hash, number uint64) bool { if has, err := db.Ancient(freezerHashTable, number); err == nil && common.BytesToHash(has) == hash { @@ -724,7 +738,6 @@ func WriteAncientBlock(db ethdb.AncientWriter, block *types.Block, receipts type return len(headerBlob) + len(bodyBlob) + len(receiptBlob) + len(tdBlob) + common.HashLength } - // DeleteBlock removes all block data associated with a hash. func DeleteBlock(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { DeleteReceipts(db, hash, number) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index a3cc66231f..6275b79f51 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -122,11 +122,6 @@ func (db *nofreezedb) AppendAncient(number uint64, hash, header, body, receipts, return errNotSupported } -// AppendAncientNoBody returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) AppendAncientNoBody(number uint64, hash, header, receipts, td []byte) error { - return errNotSupported -} - // TruncateAncients returns an error as we don't have a backing chain freezer. func (db *nofreezedb) TruncateAncients(items uint64) error { return errNotSupported @@ -162,6 +157,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st if err != nil { return nil, err } + offset := ReadOffSetOfAncientFreezer(db) // Since the freezer can be stored separately from the user's key-value database, // there's a fairly high probability that the user requests invalid combinations // of the freezer and database. Ensure that we don't shoot ourselves in the foot @@ -184,46 +180,48 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // If the genesis hash is empty, we have a new key-value store, so nothing to // validate in this method. If, however, the genesis hash is not nil, compare // it to the freezer content. - 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 - // the freezer and the key-value store. - frgenesis, err := frdb.Ancient(freezerHashTable, 0) - if err != nil { - return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) - } else if !bytes.Equal(kvgenesis, frgenesis) { - return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) - } - // Key-value store and freezer belong to the same network. Ensure that they - // are contiguous, otherwise we might end up with a non-functional freezer. - if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { - // Subsequent header after the freezer limit is missing from the database. - // Reject startup is the database has a more recent head. - if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { - return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) + if offset == 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 + // the freezer and the key-value store. + frgenesis, err := frdb.Ancient(freezerHashTable, 0) + if err != nil { + return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) + } else if !bytes.Equal(kvgenesis, frgenesis) { + return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) } - // Database contains only older data than the freezer, this happens if the - // state was wiped and reinited from an existing freezer. - } - // Otherwise, key-value store continues where the freezer left off, all is fine. - // We might have duplicate blocks (crash after freezer write but before key-value - // store deletion, but that's fine). - } else { - // If the freezer is empty, ensure nothing was moved yet from the key-value - // store, otherwise we'll end up missing data. We check block #1 to decide - // if we froze anything previously or not, but do take care of databases with - // only the genesis block. - if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { - // Key-value store contains more data than the genesis block, make sure we - // didn't freeze anything yet. - if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { - return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") + // Key-value store and freezer belong to the same network. Ensure that they + // are contiguous, otherwise we might end up with a non-functional freezer. + if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { + // Subsequent header after the freezer limit is missing from the database. + // Reject startup is the database has a more recent head. + if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { + return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) + } + // Database contains only older data than the freezer, this happens if the + // state was wiped and reinited from an existing freezer. + } + // Otherwise, key-value store continues where the freezer left off, all is fine. + // We might have duplicate blocks (crash after freezer write but before key-value + // store deletion, but that's fine). + } else { + // If the freezer is empty, ensure nothing was moved yet from the key-value + // store, otherwise we'll end up missing data. We check block #1 to decide + // if we froze anything previously or not, but do take care of databases with + // only the genesis block. + if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { + // Key-value store contains more data than the genesis block, make sure we + // didn't freeze anything yet. + if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { + return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") + } + // Block #1 is still in the database, we're allowed to init a new feezer } - // Block #1 is still in the database, we're allowed to init a new feezer + // Otherwise, the head header is still the genesis, we're allowed to init a new + // feezer. } - // Otherwise, the head header is still the genesis, we're allowed to init a new - // feezer. } } // Freezer is consistent with the key-value database, permit combining the two @@ -236,6 +234,42 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }, nil } +// 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. +//Without goroutine of freeze running +func NewDatabaseWithFreezerForPruneBlock(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { + // Create the idle freezer instance + frdb, err := newFreezer(freezer, namespace, readonly) + if err != nil { + return nil, err + } + + return &freezerdb{ + KeyValueStore: db, + AncientStore: frdb, + }, nil +} + +// 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 NewDatabaseWithFreezerBackup(offset uint64, db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { + // Create the idle freezer instance + frdb, err := newFreezer(freezer, namespace, readonly) + if err != nil { + return nil, err + } + + //Assign the new offset to the new backup freezer while creating freezer + frdb.offset = offset + + return &freezerdb{ + KeyValueStore: db, + AncientStore: frdb, + }, nil +} + // NewMemoryDatabase creates an ephemeral in-memory key-value database without a // freezer moving immutable chain segments into cold storage. func NewMemoryDatabase() ethdb.Database { @@ -274,6 +308,37 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer return frdb, nil } +// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a +// freezer moving immutable chain segments into cold storage. +func NewLevelDBDatabaseWithFreezerForPruneBlock(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 := NewDatabaseWithFreezerForPruneBlock(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 NewLevelDBDatabaseWithFreezerBackup(offset uint64, 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 + } + db, err := NewDatabaseWithFreezerBackup(offset, kvdb, freezer, namespace, readonly) + if err != nil { + kvdb.Close() + return nil, err + } + + return db, nil +} + type counter uint64 func (c counter) String() string { diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 94b99a64eb..14f14f98ff 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -85,6 +85,8 @@ type freezer struct { quit chan struct{} closeOnce sync.Once + + offset uint64 } // newFreezer creates a chain freezer that moves ancient chain data into @@ -164,7 +166,7 @@ func (f *freezer) Close() error { // in the freezer. func (f *freezer) HasAncient(kind string, number uint64) (bool, error) { if table := f.tables[kind]; table != nil { - return table.has(number), nil + return table.has(number - f.offset), nil } return false, nil } @@ -172,7 +174,7 @@ func (f *freezer) HasAncient(kind string, number uint64) (bool, error) { // Ancient retrieves an ancient binary blob from the append-only immutable files. func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { if table := f.tables[kind]; table != nil { - return table.Retrieve(number) + return table.Retrieve(number - f.offset) } return nil, errUnknownTable } @@ -201,7 +203,7 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td return errReadOnly } // Ensure the binary blobs we are appending is continuous with freezer. - if atomic.LoadUint64(&f.frozen) != number { + if atomic.LoadUint64(&f.frozen) != number-f.offset { return errOutOrderInsertion } // Rollback all inserted data if any insertion below failed to ensure @@ -313,6 +315,11 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { continue } number := ReadHeaderNumber(nfdb, hash) + + //minus the freezer offset + if number != nil { + *number = *number - f.offset + } threshold := atomic.LoadUint64(&f.threshold) switch { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index b4fb99e451..4187966b88 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -69,6 +69,7 @@ var ( // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") + offSetOfAncientFreezer = []byte("OffSetOfAncientFreezer") // badBlockKey tracks the list of bad blocks seen by local badBlockKey = []byte("InvalidBlock") diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 437eec52a6..1c5198f0c3 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -93,9 +92,7 @@ type BlockPruner struct { db ethdb.Database chaindbDir string ancientdbDir string - headHeader *types.Header n *node.Node - genesis *core.Genesis } // NewPruner creates the pruner instance. @@ -128,14 +125,13 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, chaindbDir, ancientdbDir string, genesis *core.Genesis) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, chaindbDir, ancientdbDir string) (*BlockPruner, error) { return &BlockPruner{ db: db, chaindbDir: chaindbDir, ancientdbDir: ancientdbDir, n: n, - genesis: genesis, }, nil } @@ -258,54 +254,65 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta } // Prune block body data -func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFreezer, namespace string, readonly bool) error { +func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFreezer, freezer, namespace string, readonly bool) error { //Back-up the necessary data within original ancient directory, create new freezer backup directory backFreezer - //db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, backFreezer, namespace, readonly) start := time.Now() - chainDbBack, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, backFreezer, namespace, readonly) + + //write the latest 128 blocks data of the ancient db + // If we can't access the freezer or it's empty, abort + chainDb, err := p.n.OpenDatabaseWithFreezerForPruneBlock(name, cache, handles, freezer, namespace, readonly) if err != nil { log.Error("Failed to open ancient database: %v", err) return err } - //write back-up data to new chainDb - //write genesis block firstly - genesis := p.genesis - if _, _, err := core.SetupGenesisBlock(chainDbBack, genesis); err != nil { - log.Error("Failed to write genesis block: %v", err) - return err - } + oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) + frozen, err := chainDb.Ancients() - //write the latest 128 blocks data of the ancient db - // If we can't access the freezer or it's empty, abort - frozen, err := p.db.Ancients() if err != nil || frozen == 0 { - return errors.New("Can't access the freezer or it's empty, abort") - } - start_index := frozen - 128 - if start_index < 0 { - start_index = 0 + return errors.New("can't access the freezer or it's empty, abort") } - //All ancient data within the most recent 128 blocks write into new ancient_back directory - chainDb := p.db - for blockNumber := start_index; blockNumber < frozen; blockNumber++ { + startBlockNumber := frozen + oldOffSet - 128 + + newOffSet := oldOffSet + frozen - 128 + + //write the new offset into db for new freezer usage + rawdb.WriteOffSetOfAncientFreezer(chainDb, newOffSet) + + blockList := make([]*types.Block, 0, 128) + receiptsList := make([]types.Receipts, 0, 128) + externTdList := make([]*big.Int, 0, 128) + + //All ancient data within the most recent 128 blocks write into memory for future new ancient_back directory usage + for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) + blockList = append(blockList, block) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) + receiptsList = append(receiptsList, receipts) // Calculate the total difficulty of the block td := rawdb.ReadTd(chainDb, blockHash, blockNumber) if td == nil { return consensus.ErrUnknownAncestor } externTd := new(big.Int).Add(block.Difficulty(), td) - rawdb.WriteAncientBlock(chainDbBack, block, receipts, externTd) + externTdList = append(externTdList, externTd) } - //chainDb.TruncateAncients(start_index - 1) chainDb.Close() - chainDbBack.Close() - log.Info("Block pruning BackUp successful", common.PrettyDuration(time.Since(start))) + chainDbBack, err := p.n.OpenDatabaseWithFreezerBackup(newOffSet, name, cache, handles, backFreezer, namespace, readonly) + if err != nil { + log.Error("Failed to open ancient database: %v", err) + return err + } + //Write into ancient_backup + for id := 0; id < len(blockList); id++ { + rawdb.WriteAncientBlock(chainDbBack, blockList[id], receiptsList[id], externTdList[id]) + } + + chainDbBack.Close() + log.Info("Block pruning BackUp successfully", common.PrettyDuration(time.Since(start))) return nil } @@ -313,13 +320,12 @@ func BlockPrune(oldAncientPath, newAncientPath string) error { //Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient if err := os.RemoveAll(oldAncientPath); err != nil { log.Error("Failed to remove old ancient directory %v", err) - return err } //Rename the new ancientdb path same to the old if err := os.Rename(newAncientPath, oldAncientPath); err != nil { - log.Error("Failed to rename new ancient directory %v", err) + log.Error("Failed to rename new ancient directory") return err } return nil diff --git a/ethdb/database.go b/ethdb/database.go index c399f45c20..40d0e01d57 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -94,7 +94,6 @@ type AncientWriter interface { // Sync flushes all in-memory ancient store data to disk. Sync() error - } // Reader contains the methods required to read data from both key-value as well as diff --git a/node/node.go b/node/node.go index c122dad32c..aff6b88e92 100644 --- a/node/node.go +++ b/node/node.go @@ -634,6 +634,72 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, 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 +// database to immutable append-only files. If the node is an ephemeral one, a +// memory database is returned. +func (n *Node) OpenDatabaseWithFreezerForPruneBlock(name string, cache, handles int, freezer, namespace string, readonly 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 { + root := n.ResolvePath(name) + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = n.ResolvePath(freezer) + } + db, err = rawdb.NewLevelDBDatabaseWithFreezerForPruneBlock(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 +// database to immutable append-only files. If the node is an ephemeral one, a +// memory database is returned. +func (n *Node) OpenDatabaseWithFreezerBackup(offset uint64, name string, cache, handles int, freezer, namespace string, readonly 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 { + root := n.ResolvePath(name) + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = n.ResolvePath(freezer) + } + db, err = rawdb.NewLevelDBDatabaseWithFreezerBackup(offset, root, cache, handles, freezer, namespace, readonly) + } + + if err == nil { + db = n.wrapDatabase(db) + } + return db, err +} + func (n *Node) OpenDiffDatabase(name string, handles int, diff, namespace string, readonly bool) (*leveldb.Database, error) { n.lock.Lock() defer n.lock.Unlock() From 021aba58ec74a43251215f0195015116a18ad517 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Fri, 17 Dec 2021 15:45:01 +0800 Subject: [PATCH 04/26] update and add unit test --- cmd/geth/pruneblock_test.go | 151 ++++++++++++++++++++++++++++++++++++ cmd/geth/snapshot.go | 9 ++- core/rawdb/database.go | 23 +++--- core/state/pruner/pruner.go | 33 ++++---- node/node.go | 15 ++-- 5 files changed, 195 insertions(+), 36 deletions(-) create mode 100644 cmd/geth/pruneblock_test.go diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go new file mode 100644 index 0000000000..a04cacb0fd --- /dev/null +++ b/cmd/geth/pruneblock_test.go @@ -0,0 +1,151 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "io/ioutil" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/pruner" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" +) + +// So we can deterministically seed different blockchains +var ( + canonicalSeed = 1 + + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +func TestOfflineBlockPrune(t *testing.T) { + datadir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Failed to create temporary datadir: %v", err) + } + os.RemoveAll(datadir) + + chaindbPath := datadir + "/chaindata" + oldAncientPath := chaindbPath + "/ancient" + newAncientPath := chaindbPath + "/ancient_back" + //create a database with ancient freezer + db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 512, utils.MakeDatabaseHandles(), oldAncientPath, "", false) + + if err != nil { + t.Fatalf("failed to create database with ancient backend") + } + + testBlockPruneForOffSetZero(t, true, db, newAncientPath, oldAncientPath, chaindbPath) + +} + +func testBlockPruneForOffSetZero(t *testing.T, full bool, db ethdb.Database, backFreezer, oldfreezer, chaindbPath string) error { + // Make chain starting from genesis + blockchain, n, err := newCanonical(t, db, ethash.NewFaker(), 92000, full, chaindbPath) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer blockchain.Stop() + + testBlockPruner, err := pruner.NewBlockPruner(db, n, oldfreezer) + if err != nil { + t.Fatalf("failed to make new blockpruner: %v", err) + } + + db.Close() + if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), backFreezer, oldfreezer, "", false); err != nil { + log.Error("Failed to back up block", "err", err) + return err + } + + return nil + +} + +// newCanonical creates a chain database, and injects a deterministic canonical +// chain. Depending on the full flag, if creates either a full block chain or a +// header only chain. +func newCanonical(t *testing.T, db ethdb.Database, engine consensus.Engine, height int, full bool, chaindbPath string) (*core.BlockChain, *node.Node, error) { + + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &core.Genesis{Config: params.TestChainConfig, Alloc: core.GenesisAlloc{address: {Balance: funds}}} + genesis = gspec.MustCommit(db) + ) + + // Initialize a fresh chain with only a genesis block + blockchain, _ := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + + // Full block-chain requested, same to GenerateChain func + blocks := makeBlockChain(genesis, height, engine, db, canonicalSeed) + _, err := blockchain.InsertChain(blocks) + nd, _ := startEthService(t, gspec, blocks, chaindbPath) + return blockchain, nd, err +} + +// makeBlockChain creates a deterministic chain of blocks rooted at parent. +func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block { + blocks, _ := core.GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) + }) + return blocks +} + +// startEthService creates a full node instance for testing. +func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block, chaindbPath string) (*node.Node, *eth.Ethereum) { + t.Helper() + + n, err := node.New(&node.Config{DataDir: chaindbPath}) + if err != nil { + t.Fatal("can't create node:", err) + } + + ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}} + ethservice, err := eth.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { + n.Close() + t.Fatal("can't import test blocks:", err) + } + ethservice.SetEtherbase(testAddr) + + return n, ethservice +} diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 409d0ae67e..ca0656f533 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -93,6 +93,7 @@ the trie clean cache with default directory will be deleted. }, Description: ` Offline prune for block data. +For AncientFlag, please specify the absolute path of node/geth/chaindata. `, }, { @@ -177,8 +178,10 @@ func pruneBlock(ctx *cli.Context) error { newAncientPath = path + "/ancient_back" } else { utils.Fatalf("Prune failed, did not specify the AncientPath %v") + return errors.New("Prune failed, did not specify the AncientPath") } + //TODO for filelock //lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "FLOCK")) // lock, _, err := fileutil.Flock(oldAncientPath) // if err != nil { @@ -196,9 +199,10 @@ func pruneBlock(ctx *cli.Context) error { case !filepath.IsAbs(oldAncientPath): oldAncientPath = stack.ResolvePath(oldAncientPath) } - pruner, err := pruner.NewBlockPruner(chaindb, stack, stack.ResolvePath(""), oldAncientPath) + pruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath) if err != nil { utils.Fatalf("Failed to create block pruner", err) + return err } if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), newAncientPath, oldAncientPath, "", false); err != nil { @@ -209,11 +213,12 @@ func pruneBlock(ctx *cli.Context) error { log.Info("geth block offline pruning backup successfully") + //After backing up successfully, rename the new ancientdb name to the original one, and delete the old ancientdb if err := pruner.BlockPrune(oldAncientPath, newAncientPath); err != nil { utils.Fatalf("Failed to prune block", err) return err } - //lock.Release() + //TODO lock.Release() log.Info("Block prune successfully") return nil } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 6275b79f51..36242c1bbf 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -180,6 +180,8 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // If the genesis hash is empty, we have a new key-value store, so nothing to // validate in this method. If, however, the genesis hash is not nil, compare // it to the freezer content. + // Only to check the followings when offset equal to 0, otherwise the block number + // in ancientdb did not start with 0, no genesis block in ancientdb as well. if offset == 0 { if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { if frozen, _ := frdb.Ancients(); frozen > 0 { @@ -234,10 +236,9 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }, nil } -// 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. -//Without goroutine of freeze running +// NewDatabaseWithFreezerForPruneBlock creates or open if existed a high level database on top of a given key- +// value data store with a freezer, but without goroutine of freezer running to avoid the uncertainty +// between kvdb and freezer goroutine when open/close db. func NewDatabaseWithFreezerForPruneBlock(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newFreezer(freezer, namespace, readonly) @@ -251,9 +252,9 @@ func NewDatabaseWithFreezerForPruneBlock(db ethdb.KeyValueStore, freezer string, }, nil } -// 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. +// NewDatabaseWithFreezerBackup creates or open if existed a high level database on top of a given key- +// value data store with a freezer, passed the params of offset, without goroutine of freezer running +//to avoid the uncertainty between kvdb and freezer goroutine when open/close db func NewDatabaseWithFreezerBackup(offset uint64, db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newFreezer(freezer, namespace, readonly) @@ -308,8 +309,8 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer return frdb, nil } -// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a -// freezer moving immutable chain segments into cold storage. +// NewLevelDBDatabaseWithFreezerForPruneBlock creates a persistent key-value database with a +// freezer. func NewLevelDBDatabaseWithFreezerForPruneBlock(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 { @@ -323,8 +324,8 @@ func NewLevelDBDatabaseWithFreezerForPruneBlock(file string, cache int, handles return frdb, nil } -// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a -// freezer moving immutable chain segments into cold storage. +// NewLevelDBDatabaseWithFreezerBackup creates a persistent key-value database with a +// freezer. func NewLevelDBDatabaseWithFreezerBackup(offset uint64, 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 { diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 1c5198f0c3..5e4311c9ca 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -90,7 +90,6 @@ type Pruner struct { type BlockPruner struct { db ethdb.Database - chaindbDir string ancientdbDir string n *node.Node } @@ -125,11 +124,10 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, chaindbDir, ancientdbDir string) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, ancientdbDir string) (*BlockPruner, error) { return &BlockPruner{ db: db, - chaindbDir: chaindbDir, ancientdbDir: ancientdbDir, n: n, }, nil @@ -253,37 +251,39 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } -// Prune block body data +// Backup the ancient data for the old ancient db, i.e. the most recent 128 blocks in ancient db. func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFreezer, freezer, namespace string, readonly bool) error { - //Back-up the necessary data within original ancient directory, create new freezer backup directory backFreezer + start := time.Now() - //write the latest 128 blocks data of the ancient db - // If we can't access the freezer or it's empty, abort chainDb, err := p.n.OpenDatabaseWithFreezerForPruneBlock(name, cache, handles, freezer, namespace, readonly) if err != nil { log.Error("Failed to open ancient database: %v", err) return err } - oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) + // Get the number of items in ancient db, frozen it is. frozen, err := chainDb.Ancients() + // Write the latest 128 blocks data of the ancient db + // If we can't access the freezer or it's empty, abort. if err != nil || frozen == 0 { return errors.New("can't access the freezer or it's empty, abort") } - startBlockNumber := frozen + oldOffSet - 128 - + // Get the actual start block number. + startBlockNumber := frozen - 128 + oldOffSet + // For every round, newoffset actually equals to the startBlockNumber in ancient db. newOffSet := oldOffSet + frozen - 128 - //write the new offset into db for new freezer usage + // Write the new offset into db for the future new freezer usage. rawdb.WriteOffSetOfAncientFreezer(chainDb, newOffSet) + // Initialize the slice to buffer the 128 block data. blockList := make([]*types.Block, 0, 128) receiptsList := make([]types.Receipts, 0, 128) externTdList := make([]*big.Int, 0, 128) - //All ancient data within the most recent 128 blocks write into memory for future new ancient_back directory usage + // All ancient data within the most recent 128 blocks write into memory buffer for future new ancient_back directory usage. for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) @@ -298,15 +298,16 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree externTd := new(big.Int).Add(block.Difficulty(), td) externTdList = append(externTdList, externTd) } - chainDb.Close() + // Create new freezer backup directory backFreezer, in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup chainDbBack, err := p.n.OpenDatabaseWithFreezerBackup(newOffSet, name, cache, handles, backFreezer, namespace, readonly) if err != nil { log.Error("Failed to open ancient database: %v", err) return err } - //Write into ancient_backup + + // Write into ancient_backup for id := 0; id < len(blockList); id++ { rawdb.WriteAncientBlock(chainDbBack, blockList[id], receiptsList[id], externTdList[id]) } @@ -317,13 +318,13 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree } func BlockPrune(oldAncientPath, newAncientPath string) error { - //Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient + // Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient if err := os.RemoveAll(oldAncientPath); err != nil { log.Error("Failed to remove old ancient directory %v", err) return err } - //Rename the new ancientdb path same to the old + // Rename the new ancientdb path same to the old if err := os.Rename(newAncientPath, oldAncientPath); err != nil { log.Error("Failed to rename new ancient directory") return err diff --git a/node/node.go b/node/node.go index aff6b88e92..f7cd1f853e 100644 --- a/node/node.go +++ b/node/node.go @@ -634,11 +634,11 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, return db, err } -// OpenDatabaseWithFreezer opens an existing database with the given name (or +// The big difference is that the freezer inside was not running to avoid the mess between kvdb and freezer +// while opening/closing db. +// OpenDatabaseWithFreezerForPruneBlock 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 -// database to immutable append-only files. If the node is an ephemeral one, a -// memory database is returned. +// also attaching a chain freezer to it. If the node is an ephemeral one, a memory database is returned. func (n *Node) OpenDatabaseWithFreezerForPruneBlock(name string, cache, handles int, freezer, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() @@ -667,10 +667,11 @@ func (n *Node) OpenDatabaseWithFreezerForPruneBlock(name string, cache, handles return db, err } -// OpenDatabaseWithFreezer opens an existing database with the given name (or +// The big difference is that the freezer inside was not running to avoid the mess between kvdb and freezer +// while opening/closing db. +// OpenDatabaseWithFreezerBackup 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 -// database to immutable append-only files. If the node is an ephemeral one, a +// also attaching a chain freezer to it. If the node is an ephemeral one, a // memory database is returned. func (n *Node) OpenDatabaseWithFreezerBackup(offset uint64, name string, cache, handles int, freezer, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() From 87904271bcdd5cbd9a19a327041264392c768a73 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 23 Dec 2021 00:21:42 +0800 Subject: [PATCH 05/26] addressed comments from walt --- .gitignore | 1 + cmd/geth/pruneblock_test.go | 4 +- cmd/geth/snapshot.go | 101 ++++++++++++++++++++---------------- cmd/utils/flags.go | 28 ++-------- core/rawdb/database.go | 81 ++++------------------------- core/state/pruner/pruner.go | 82 +++++++++++++++++------------ node/node.go | 71 +------------------------ 7 files changed, 120 insertions(+), 248 deletions(-) diff --git a/.gitignore b/.gitignore index 3d05da8816..25ae6adc8e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ profile.cov /dashboard/assets/package-lock.json **/yarn-error.log +cmd/geth/node/ diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index a04cacb0fd..8be9abcbee 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -78,13 +78,13 @@ func testBlockPruneForOffSetZero(t *testing.T, full bool, db ethdb.Database, bac } defer blockchain.Stop() - testBlockPruner, err := pruner.NewBlockPruner(db, n, oldfreezer) + testBlockPruner, err := pruner.NewBlockPruner(db, n, oldfreezer, backFreezer) if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } db.Close() - if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), backFreezer, oldfreezer, "", false); err != nil { + if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false); err != nil { log.Error("Failed to back up block", "err", err) return err } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index ca0656f533..077a78c964 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -29,9 +29,12 @@ import ( "github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/prometheus/tsdb/fileutil" cli "gopkg.in/urfave/cli.v1" ) @@ -82,18 +85,16 @@ the trie clean cache with default directory will be deleted. `, }, { - Name: "prune-block", - Usage: "Prune block data offline", - ArgsUsage: "", - Action: utils.MigrateFlags(pruneBlock), - Category: "MISCELLANEOUS COMMANDS", + Name: "prune-block", + Usage: "Prune block data offline", + Action: utils.MigrateFlags(pruneBlock), + Category: "MISCELLANEOUS COMMANDS", Flags: []cli.Flag{ utils.DataDirFlag, utils.AncientFlag, }, Description: ` Offline prune for block data. -For AncientFlag, please specify the absolute path of node/geth/chaindata. `, }, { @@ -165,50 +166,61 @@ It's also usable without snapshot enabled. } ) +func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { + chaindb := utils.MakeChainDatabase(ctx, stack, false, true) + defer chaindb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + return nil, errors.New("failed to load head block") + } + //Make sure the MPT and snapshot matches before pruning, otherwise the node can not start. + _, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)), headBlock.Root(), false, false, false) + if err != nil { + return nil, err // The relevant snapshot(s) might not exist + } + return chaindb, nil +} + func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - - chaindb := utils.MakeChainDatabaseForBlockPrune(ctx, stack, false) - chaindb.Close() - + chaindb, err := accessDb(ctx, stack) + if err != nil { + utils.Fatalf("MPT and snapshot sanity check failed: %v", err) + return err + } var oldAncientPath, newAncientPath string - if path := getAncientPath(ctx); path != "" { - oldAncientPath = path + "/ancient" - newAncientPath = path + "/ancient_back" + path, _ := filepath.Split(ctx.GlobalString(utils.AncientFlag.Name)) + if path != "" { + if !filepath.IsAbs(path) { + path = stack.ResolvePath(path) + } + oldAncientPath = path + "ancient" + newAncientPath = path + "ancient_back" } else { - utils.Fatalf("Prune failed, did not specify the AncientPath %v") + utils.Fatalf("Prune failed, did not specify the AncientPath") return errors.New("Prune failed, did not specify the AncientPath") } - //TODO for filelock - //lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "FLOCK")) - // lock, _, err := fileutil.Flock(oldAncientPath) - // if err != nil { - // return err - // } - // _, err = os.Open(oldAncientPath) - // if err != nil { - // utils.Fatalf("Failed to read genesis file: %v", err) - // } - for _, name := range []string{"chaindata"} { - root := stack.ResolvePath(name) - switch { - case oldAncientPath == "": - oldAncientPath = filepath.Join(root, "ancient") - case !filepath.IsAbs(oldAncientPath): - oldAncientPath = stack.ResolvePath(oldAncientPath) - } - pruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath) - if err != nil { - utils.Fatalf("Failed to create block pruner", err) - return err - } + lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "RPUNEFLOCK")) + if err != nil { + return err + } - if err := pruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), newAncientPath, oldAncientPath, "", false); err != nil { - log.Error("Failed to back up block", "err", err) - return err - } + name := "chaindata" + if !filepath.IsAbs(oldAncientPath) { + oldAncientPath = stack.ResolvePath(oldAncientPath) + } + + blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath) + if err != nil { + utils.Fatalf("Failed to create block pruner: %v", err) + return err + } + + if err := blockpruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { + log.Error("Failed to back up block", "err", err) + return err } log.Info("geth block offline pruning backup successfully") @@ -218,15 +230,12 @@ func pruneBlock(ctx *cli.Context) error { utils.Fatalf("Failed to prune block", err) return err } - //TODO lock.Release() + + lock.Release() log.Info("Block prune successfully") return nil } -func getAncientPath(ctx *cli.Context) string { - return ctx.GlobalString(utils.AncientFlag.Name) -} - func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 202b463a1c..583dd24a00 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1883,7 +1883,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, args ...bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1893,32 +1893,10 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. ) if ctx.GlobalString(SyncModeFlag.Name) == "light" { name := "lightchaindata" - chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) + chainDb, err = stack.OpenDatabase(name, cache, handles, "", args[0]) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) - } - if err != nil { - Fatalf("Could not open database: %v", err) - } - return chainDb -} - -// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabaseForBlockPrune(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database { - var ( - cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 - handles = MakeDatabaseHandles() - - err error - chainDb ethdb.Database - ) - if ctx.GlobalString(SyncModeFlag.Name) == "light" { - name := "lightchaindata" - chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) - } else { - name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezerForPruneBlock(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", args...) } if err != nil { Fatalf("Could not open database: %v", err) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 36242c1bbf..45ffad53fe 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -151,13 +151,16 @@ 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, args ...bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, readonly) + frdb, err := newFreezer(freezer, namespace, args[0]) if err != nil { return nil, err } offset := ReadOffSetOfAncientFreezer(db) + + frdb.offset = offset + // Since the freezer can be stored separately from the user's key-value database, // there's a fairly high probability that the user requests invalid combinations // of the freezer and database. Ensure that we don't shoot ourselves in the foot @@ -227,7 +230,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 len(args) == 1 && !frdb.readonly { go frdb.freeze(db) } return &freezerdb{ @@ -236,41 +239,6 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }, nil } -// NewDatabaseWithFreezerForPruneBlock creates or open if existed a high level database on top of a given key- -// value data store with a freezer, but without goroutine of freezer running to avoid the uncertainty -// between kvdb and freezer goroutine when open/close db. -func NewDatabaseWithFreezerForPruneBlock(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { - // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, readonly) - if err != nil { - return nil, err - } - - return &freezerdb{ - KeyValueStore: db, - AncientStore: frdb, - }, nil -} - -// NewDatabaseWithFreezerBackup creates or open if existed a high level database on top of a given key- -// value data store with a freezer, passed the params of offset, without goroutine of freezer running -//to avoid the uncertainty between kvdb and freezer goroutine when open/close db -func NewDatabaseWithFreezerBackup(offset uint64, db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { - // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, readonly) - if err != nil { - return nil, err - } - - //Assign the new offset to the new backup freezer while creating freezer - frdb.offset = offset - - return &freezerdb{ - KeyValueStore: db, - AncientStore: frdb, - }, nil -} - // NewMemoryDatabase creates an ephemeral in-memory key-value database without a // freezer moving immutable chain segments into cold storage. func NewMemoryDatabase() ethdb.Database { @@ -296,12 +264,12 @@ 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) { - kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, args ...bool) (ethdb.Database, error) { + kvdb, err := leveldb.New(file, cache, handles, namespace, args[0]) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, args...) if err != nil { kvdb.Close() return nil, err @@ -309,37 +277,6 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer return frdb, nil } -// NewLevelDBDatabaseWithFreezerForPruneBlock creates a persistent key-value database with a -// freezer. -func NewLevelDBDatabaseWithFreezerForPruneBlock(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 := NewDatabaseWithFreezerForPruneBlock(kvdb, freezer, namespace, readonly) - if err != nil { - kvdb.Close() - return nil, err - } - return frdb, nil -} - -// NewLevelDBDatabaseWithFreezerBackup creates a persistent key-value database with a -// freezer. -func NewLevelDBDatabaseWithFreezerBackup(offset uint64, 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 - } - db, err := NewDatabaseWithFreezerBackup(offset, kvdb, freezer, namespace, readonly) - if err != nil { - kvdb.Close() - return nil, err - } - - return db, nil -} - type counter uint64 func (c counter) String() string { diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 5e4311c9ca..44518e4dc8 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -57,6 +57,9 @@ const ( // triggering range compaction. It's a quite arbitrary number but just // to avoid triggering range compaction because of small deletion. rangeCompactionThreshold = 100000 + + // back up the most recent 128 blocks in ancientdb + blockPruneBackUpBlockNumber = 128 ) var ( @@ -89,9 +92,10 @@ type Pruner struct { } type BlockPruner struct { - db ethdb.Database - ancientdbDir string - n *node.Node + db ethdb.Database + oldAncientPath string + newAncientPath string + n *node.Node } // NewPruner creates the pruner instance. @@ -124,12 +128,13 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, ancientdbDir string) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string) (*BlockPruner, error) { return &BlockPruner{ - db: db, - ancientdbDir: ancientdbDir, - n: n, + db: db, + oldAncientPath: oldAncientPath, + newAncientPath: newAncientPath, + n: n, }, nil } @@ -251,38 +256,31 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } -// Backup the ancient data for the old ancient db, i.e. the most recent 128 blocks in ancient db. -func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFreezer, freezer, namespace string, readonly bool) error { - - start := time.Now() - - chainDb, err := p.n.OpenDatabaseWithFreezerForPruneBlock(name, cache, handles, freezer, namespace, readonly) +func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, namespace string, args ...bool) ([]*types.Block, []types.Receipts, []*big.Int, error) { + chainDb, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, args...) if err != nil { log.Error("Failed to open ancient database: %v", err) - return err + return nil, nil, nil, err } - oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) + defer chainDb.Close() + // Get the number of items in ancient db, frozen it is. frozen, err := chainDb.Ancients() - // Write the latest 128 blocks data of the ancient db // If we can't access the freezer or it's empty, abort. if err != nil || frozen == 0 { - return errors.New("can't access the freezer or it's empty, abort") + log.Error("can't access the freezer or it's empty, abort") + return nil, nil, nil, errors.New("can't access the freezer or it's empty, abort") } - // Get the actual start block number. - startBlockNumber := frozen - 128 + oldOffSet - // For every round, newoffset actually equals to the startBlockNumber in ancient db. - newOffSet := oldOffSet + frozen - 128 - // Write the new offset into db for the future new freezer usage. - rawdb.WriteOffSetOfAncientFreezer(chainDb, newOffSet) + oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) + // Get the actual start block number. + startBlockNumber := frozen - blockPruneBackUpBlockNumber + oldOffSet // Initialize the slice to buffer the 128 block data. - blockList := make([]*types.Block, 0, 128) - receiptsList := make([]types.Receipts, 0, 128) - externTdList := make([]*big.Int, 0, 128) - + blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber) + receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber) + externTdList := make([]*big.Int, 0, blockPruneBackUpBlockNumber) // All ancient data within the most recent 128 blocks write into memory buffer for future new ancient_back directory usage. for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) @@ -293,27 +291,43 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, backFree // Calculate the total difficulty of the block td := rawdb.ReadTd(chainDb, blockHash, blockNumber) if td == nil { - return consensus.ErrUnknownAncestor + return nil, nil, nil, consensus.ErrUnknownAncestor } externTd := new(big.Int).Add(block.Difficulty(), td) externTdList = append(externTdList, externTd) } - chainDb.Close() - // Create new freezer backup directory backFreezer, in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup - chainDbBack, err := p.n.OpenDatabaseWithFreezerBackup(newOffSet, name, cache, handles, backFreezer, namespace, readonly) + // For every round, newoffset actually equals to the startBlockNumber in ancient db. + newOffSet := oldOffSet + frozen - 128 + // Write the new offset into db for the future new freezer usage. + rawdb.WriteOffSetOfAncientFreezer(chainDb, newOffSet) + return blockList, receiptsList, externTdList, nil +} + +// Backup the ancient data for the old ancient db, i.e. the most recent 128 blocks in ancient db. +func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespace string, readonly bool) error { + + start := time.Now() + isBlockPruner := true + + blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly, isBlockPruner) + if err != nil { + return err + } + + // Create new freezer backup directory backFreezer, in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup. + chainDbBack, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, p.newAncientPath, namespace, readonly, isBlockPruner) if err != nil { - log.Error("Failed to open ancient database: %v", err) return err } + defer chainDbBack.Close() // Write into ancient_backup for id := 0; id < len(blockList); id++ { rawdb.WriteAncientBlock(chainDbBack, blockList[id], receiptsList[id], externTdList[id]) } - chainDbBack.Close() - log.Info("Block pruning BackUp successfully", common.PrettyDuration(time.Since(start))) + log.Info("Block pruning BackUp successfully", "time duration since start is", common.PrettyDuration(time.Since(start))) return nil } diff --git a/node/node.go b/node/node.go index f7cd1f853e..af1595bccb 100644 --- a/node/node.go +++ b/node/node.go @@ -606,7 +606,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, args ...bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -625,74 +625,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) - } - - if err == nil { - db = n.wrapDatabase(db) - } - return db, err -} - -// The big difference is that the freezer inside was not running to avoid the mess between kvdb and freezer -// while opening/closing db. -// OpenDatabaseWithFreezerForPruneBlock 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. If the node is an ephemeral one, a memory database is returned. -func (n *Node) OpenDatabaseWithFreezerForPruneBlock(name string, cache, handles int, freezer, namespace string, readonly 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 { - root := n.ResolvePath(name) - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = n.ResolvePath(freezer) - } - db, err = rawdb.NewLevelDBDatabaseWithFreezerForPruneBlock(root, cache, handles, freezer, namespace, readonly) - } - - if err == nil { - db = n.wrapDatabase(db) - } - return db, err -} - -// The big difference is that the freezer inside was not running to avoid the mess between kvdb and freezer -// while opening/closing db. -// OpenDatabaseWithFreezerBackup 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. If the node is an ephemeral one, a -// memory database is returned. -func (n *Node) OpenDatabaseWithFreezerBackup(offset uint64, name string, cache, handles int, freezer, namespace string, readonly 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 { - root := n.ResolvePath(name) - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = n.ResolvePath(freezer) - } - db, err = rawdb.NewLevelDBDatabaseWithFreezerBackup(offset, root, cache, handles, freezer, namespace, readonly) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, args...) } if err == nil { From fccd50e3784e4bd4cb3f0dc2c535f434f23a49d5 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Sun, 26 Dec 2021 07:41:45 +0800 Subject: [PATCH 06/26] Addressed comments from walt and Igor --- cmd/geth/pruneblock_test.go | 214 +++++++++++++++++++++++++----------- cmd/geth/snapshot.go | 27 +++-- core/rawdb/database.go | 78 ++++++------- core/state/pruner/pruner.go | 19 ++-- 4 files changed, 208 insertions(+), 130 deletions(-) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 8be9abcbee..40faeb7a40 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -17,10 +17,16 @@ package main import ( + "bytes" + "encoding/hex" + "fmt" "io/ioutil" "math/big" "os" + "path/filepath" + "reflect" "testing" + "time" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" @@ -33,20 +39,28 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" ) -// So we can deterministically seed different blockchains var ( - canonicalSeed = 1 - - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - // testAddr is the Ethereum address of the tester account. - testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + canonicalSeed = 1 + blockPruneBackUpBlockNumber = 128 + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + balance = big.NewInt(10000000) + gspec = &core.Genesis{Config: params.TestChainConfig, Alloc: core.GenesisAlloc{address: {Balance: balance}}} + signer = types.LatestSigner(gspec.Config) + config = &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, // Disable snapshot + TriesInMemory: 128, + } + engine = ethash.NewFullFaker() ) func TestOfflineBlockPrune(t *testing.T) { @@ -56,96 +70,162 @@ func TestOfflineBlockPrune(t *testing.T) { } os.RemoveAll(datadir) - chaindbPath := datadir + "/chaindata" - oldAncientPath := chaindbPath + "/ancient" - newAncientPath := chaindbPath + "/ancient_back" - //create a database with ancient freezer - db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 512, utils.MakeDatabaseHandles(), oldAncientPath, "", false) + chaindbPath := filepath.Join(datadir, "chaindata") + oldAncientPath := filepath.Join(chaindbPath, "ancient") + newAncientPath := filepath.Join(chaindbPath, "ancient_back") + db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath) + node, _ := startEthService(t, gspec, blocks, chaindbPath) + defer node.Close() + + testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath) if err != nil { - t.Fatalf("failed to create database with ancient backend") + t.Fatalf("failed to make new blockpruner: %v", err) + } + if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false); err != nil { + t.Fatalf("Failed to back up block: %v", err) } - testBlockPruneForOffSetZero(t, true, db, newAncientPath, oldAncientPath, chaindbPath) + dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false) + if err != nil { + t.Fatalf("failed to create database with ancient backend") + } + defer dbBack.Close() + + //check against if the backup data matched original one + for blockNumber := startBlockNumber; blockNumber < startBlockNumber+128; blockNumber++ { + blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber) + block := rawdb.ReadBlock(dbBack, blockHash, blockNumber) + if reflect.DeepEqual(block, blockList[blockNumber-startBlockNumber]) { + t.Fatalf("block data did not match between oldDb and backupDb") + } + + receipts := rawdb.ReadRawReceipts(dbBack, blockHash, blockNumber) + if err := checkReceiptsRLP(receipts, receiptsList[blockNumber-startBlockNumber]); err != nil { + t.Fatalf("receipts did not match between oldDb and backupDb") + } + // // Calculate the total difficulty of the block + td := rawdb.ReadTd(dbBack, blockHash, blockNumber) + if td == nil { + t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor) + } + externTd := new(big.Int).Add(block.Difficulty(), td) + if reflect.DeepEqual(externTd, externTdList[blockNumber-startBlockNumber]) { + t.Fatalf("externTd did not match between oldDb and backupDb") + } + } + //check if ancientDb freezer replaced successfully + testBlockPruner.AncientDbReplacer() + if _, err := os.Stat(newAncientPath); err != nil { + if !os.IsNotExist(err) { + t.Fatalf("ancientDb replaced unsuccessfully") + } + } + if _, err := os.Stat(oldAncientPath); err != nil { + t.Fatalf("ancientDb replaced unsuccessfully") + } } -func testBlockPruneForOffSetZero(t *testing.T, full bool, db ethdb.Database, backFreezer, oldfreezer, chaindbPath string) error { - // Make chain starting from genesis - blockchain, n, err := newCanonical(t, db, ethash.NewFaker(), 92000, full, chaindbPath) +func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { + //create a database with ancient freezer + db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false) if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) + t.Fatalf("failed to create database with ancient backend") } - defer blockchain.Stop() - - testBlockPruner, err := pruner.NewBlockPruner(db, n, oldfreezer, backFreezer) + defer db.Close() + genesis := gspec.MustCommit(db) + // Initialize a fresh chain with only a genesis block + blockchain, err := core.NewBlockChain(db, config, gspec.Config, engine, vm.Config{}, nil, nil) if err != nil { - t.Fatalf("failed to make new blockpruner: %v", err) + t.Fatalf("Failed to create chain: %v", err) } - db.Close() - if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false); err != nil { - log.Error("Failed to back up block", "err", err) - return err - } - - return nil + // Make chain starting from genesis -} + blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 200, func(i int, block *core.BlockGen) { + block.SetCoinbase(common.Address{0: byte(canonicalSeed), 19: byte(i)}) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx) + block.SetDifficulty(big.NewInt(1000000)) + }) + if _, err := blockchain.InsertChain(blocks); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } -// newCanonical creates a chain database, and injects a deterministic canonical -// chain. Depending on the full flag, if creates either a full block chain or a -// header only chain. -func newCanonical(t *testing.T, db ethdb.Database, engine consensus.Engine, height int, full bool, chaindbPath string) (*core.BlockChain, *node.Node, error) { + // Force run a freeze cycle + type freezer interface { + Freeze(threshold uint64) error + Ancients() (uint64, error) + } + db.(freezer).Freeze(10) - var ( - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000) - gspec = &core.Genesis{Config: params.TestChainConfig, Alloc: core.GenesisAlloc{address: {Balance: funds}}} - genesis = gspec.MustCommit(db) - ) + frozen, err := db.Ancients() + //make sure there're frozen items + if err != nil || frozen == 0 { + t.Fatalf("Failed to import canonical chain start: %v", err) + } - // Initialize a fresh chain with only a genesis block - blockchain, _ := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + oldOffSet := rawdb.ReadOffSetOfAncientFreezer(db) + // Get the actual start block number. + startBlockNumber := frozen - 128 + oldOffSet + // Initialize the slice to buffer the 128 block data. + blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber) + receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber) + externTdList := make([]*big.Int, 0, blockPruneBackUpBlockNumber) + // All ancient data within the most recent 128 blocks write into memory buffer for future new ancient_back directory usage. + for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { + blockHash := rawdb.ReadCanonicalHash(db, blockNumber) + block := rawdb.ReadBlock(db, blockHash, blockNumber) + blockList = append(blockList, block) + receipts := rawdb.ReadRawReceipts(db, blockHash, blockNumber) + receiptsList = append(receiptsList, receipts) + // Calculate the total difficulty of the block + td := rawdb.ReadTd(db, blockHash, blockNumber) + if td == nil { + t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor) + } + externTd := new(big.Int).Add(block.Difficulty(), td) + externTdList = append(externTdList, externTd) + } - // Full block-chain requested, same to GenerateChain func - blocks := makeBlockChain(genesis, height, engine, db, canonicalSeed) - _, err := blockchain.InsertChain(blocks) - nd, _ := startEthService(t, gspec, blocks, chaindbPath) - return blockchain, nd, err + return db, blocks, blockList, receiptsList, externTdList, startBlockNumber, blockchain } -// makeBlockChain creates a deterministic chain of blocks rooted at parent. -func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block { - blocks, _ := core.GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *core.BlockGen) { - b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) - }) - return blocks +func checkReceiptsRLP(have, want types.Receipts) error { + if len(have) != len(want) { + return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want)) + } + for i := 0; i < len(want); i++ { + rlpHave, err := rlp.EncodeToBytes(have[i]) + if err != nil { + return err + } + rlpWant, err := rlp.EncodeToBytes(want[i]) + if err != nil { + return err + } + if !bytes.Equal(rlpHave, rlpWant) { + return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) + } + } + return nil } // startEthService creates a full node instance for testing. func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block, chaindbPath string) (*node.Node, *eth.Ethereum) { t.Helper() - n, err := node.New(&node.Config{DataDir: chaindbPath}) if err != nil { t.Fatal("can't create node:", err) } - ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}} - ethservice, err := eth.New(n, ethcfg) - if err != nil { - t.Fatal("can't create eth service:", err) - } if err := n.Start(); err != nil { t.Fatal("can't start node:", err) } - if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { - n.Close() - t.Fatal("can't import test blocks:", err) - } - ethservice.SetEtherbase(testAddr) - return n, ethservice + return n, nil } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 077a78c964..e06bdf1f57 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -94,7 +94,13 @@ the trie clean cache with default directory will be deleted. utils.AncientFlag, }, Description: ` -Offline prune for block data. +geth offline prune-block for block data in ancientdb. +will prune all the old block data in ancientdb except for the the last 128 blocks in ancientdb. +the brief workflow is to backup the last 128 blocks in original ancientdb into new ancient_backup, +then delete the original ancientdb dir and rename the ancient_backup to original one for replacement. +The purpose of doing it is because the block data will be moved into the ancient store when it +becomes old enough(exceed the Threshold 90000), the disk usage will be very large over time, so it's very +necessary to do block data prune, this feature takes very short time in even seconds level. `, }, { @@ -186,7 +192,6 @@ func pruneBlock(ctx *cli.Context) error { defer stack.Close() chaindb, err := accessDb(ctx, stack) if err != nil { - utils.Fatalf("MPT and snapshot sanity check failed: %v", err) return err } var oldAncientPath, newAncientPath string @@ -195,29 +200,24 @@ func pruneBlock(ctx *cli.Context) error { if !filepath.IsAbs(path) { path = stack.ResolvePath(path) } - oldAncientPath = path + "ancient" - newAncientPath = path + "ancient_back" + oldAncientPath = filepath.Join(path, "ancient") + newAncientPath = filepath.Join(path, "ancient_back") } else { - utils.Fatalf("Prune failed, did not specify the AncientPath") - return errors.New("Prune failed, did not specify the AncientPath") + return errors.New("prune failed, did not specify the AncientPath") } lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "RPUNEFLOCK")) if err != nil { + log.Error("file lock existed, please wait for the lock release", "err", err) return err } - name := "chaindata" - if !filepath.IsAbs(oldAncientPath) { - oldAncientPath = stack.ResolvePath(oldAncientPath) - } - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath) if err != nil { - utils.Fatalf("Failed to create block pruner: %v", err) return err } + name := "chaindata" if err := blockpruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { log.Error("Failed to back up block", "err", err) return err @@ -226,8 +226,7 @@ func pruneBlock(ctx *cli.Context) error { log.Info("geth block offline pruning backup successfully") //After backing up successfully, rename the new ancientdb name to the original one, and delete the old ancientdb - if err := pruner.BlockPrune(oldAncientPath, newAncientPath); err != nil { - utils.Fatalf("Failed to prune block", err) + if err := blockpruner.AncientDbReplacer(); err != nil { return err } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 45ffad53fe..0fea1ebd32 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -185,50 +185,50 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // it to the freezer content. // Only to check the followings when offset equal to 0, otherwise the block number // in ancientdb did not start with 0, no genesis block in ancientdb as well. - if offset == 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 - // the freezer and the key-value store. - frgenesis, err := frdb.Ancient(freezerHashTable, 0) - if err != nil { - return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) - } else if !bytes.Equal(kvgenesis, frgenesis) { - return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) - } - // Key-value store and freezer belong to the same network. Ensure that they - // are contiguous, otherwise we might end up with a non-functional freezer. - if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { - // Subsequent header after the freezer limit is missing from the database. - // Reject startup is the database has a more recent head. - if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { - return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) - } - // Database contains only older data than the freezer, this happens if the - // state was wiped and reinited from an existing freezer. + + if kvgenesis, _ := db.Get(headerHashKey(0)); offset == 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 + // the freezer and the key-value store. + frgenesis, err := frdb.Ancient(freezerHashTable, 0) + if err != nil { + return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) + } else if !bytes.Equal(kvgenesis, frgenesis) { + return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) + } + // Key-value store and freezer belong to the same network. Ensure that they + // are contiguous, otherwise we might end up with a non-functional freezer. + if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { + // Subsequent header after the freezer limit is missing from the database. + // Reject startup is the database has a more recent head. + if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { + return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) } - // Otherwise, key-value store continues where the freezer left off, all is fine. - // We might have duplicate blocks (crash after freezer write but before key-value - // store deletion, but that's fine). - } else { - // If the freezer is empty, ensure nothing was moved yet from the key-value - // store, otherwise we'll end up missing data. We check block #1 to decide - // if we froze anything previously or not, but do take care of databases with - // only the genesis block. - if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { - // Key-value store contains more data than the genesis block, make sure we - // didn't freeze anything yet. - if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { - return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") - } - // Block #1 is still in the database, we're allowed to init a new feezer + // Database contains only older data than the freezer, this happens if the + // state was wiped and reinited from an existing freezer. + } + // Otherwise, key-value store continues where the freezer left off, all is fine. + // We might have duplicate blocks (crash after freezer write but before key-value + // store deletion, but that's fine). + } else { + // If the freezer is empty, ensure nothing was moved yet from the key-value + // store, otherwise we'll end up missing data. We check block #1 to decide + // if we froze anything previously or not, but do take care of databases with + // only the genesis block. + if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { + // Key-value store contains more data than the genesis block, make sure we + // didn't freeze anything yet. + if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { + return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") } - // Otherwise, the head header is still the genesis, we're allowed to init a new - // feezer. + // Block #1 is still in the database, we're allowed to init a new feezer } + // Otherwise, the head header is still the genesis, we're allowed to init a new + // feezer. } } + // Freezer is consistent with the key-value database, permit combining the two if len(args) == 1 && !frdb.readonly { go frdb.freeze(db) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 44518e4dc8..0b85bfc1ed 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -95,7 +95,7 @@ type BlockPruner struct { db ethdb.Database oldAncientPath string newAncientPath string - n *node.Node + node *node.Node } // NewPruner creates the pruner instance. @@ -134,7 +134,7 @@ func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientP db: db, oldAncientPath: oldAncientPath, newAncientPath: newAncientPath, - n: n, + node: n, }, nil } @@ -257,9 +257,9 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta } func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, namespace string, args ...bool) ([]*types.Block, []types.Receipts, []*big.Int, error) { - chainDb, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, args...) + chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, args...) if err != nil { - log.Error("Failed to open ancient database: %v", err) + log.Error("Failed to open ancient database", "err=", err) return nil, nil, nil, err } defer chainDb.Close() @@ -308,15 +308,14 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, name func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespace string, readonly bool) error { start := time.Now() - isBlockPruner := true - blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly, isBlockPruner) + blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly, true) if err != nil { return err } // Create new freezer backup directory backFreezer, in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup. - chainDbBack, err := p.n.OpenDatabaseWithFreezer(name, cache, handles, p.newAncientPath, namespace, readonly, isBlockPruner) + chainDbBack, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.newAncientPath, namespace, readonly, true) if err != nil { return err } @@ -331,15 +330,15 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespac return nil } -func BlockPrune(oldAncientPath, newAncientPath string) error { +func (p *BlockPruner) AncientDbReplacer() error { // Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient - if err := os.RemoveAll(oldAncientPath); err != nil { + if err := os.RemoveAll(p.oldAncientPath); err != nil { log.Error("Failed to remove old ancient directory %v", err) return err } // Rename the new ancientdb path same to the old - if err := os.Rename(newAncientPath, oldAncientPath); err != nil { + if err := os.Rename(p.newAncientPath, p.oldAncientPath); err != nil { log.Error("Failed to rename new ancient directory") return err } From be04eba35133bfd00970fac3c14f3d7e3a0222b8 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Mon, 27 Dec 2021 17:50:40 +0800 Subject: [PATCH 07/26] ensure MPT and snapshot matched --- cmd/geth/snapshot.go | 83 ++++++++++++++++++++++++++++++++++++- cmd/utils/flags.go | 6 +-- core/rawdb/database.go | 12 +++--- core/state/pruner/pruner.go | 1 - node/node.go | 4 +- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index e06bdf1f57..d2d22cecfb 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -19,6 +19,7 @@ package main import ( "bytes" "errors" + "fmt" "path/filepath" "time" @@ -175,15 +176,95 @@ It's also usable without snapshot enabled. func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { chaindb := utils.MakeChainDatabase(ctx, stack, false, true) defer chaindb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { return nil, errors.New("failed to load head block") } + headHeader := headBlock.Header() //Make sure the MPT and snapshot matches before pruning, otherwise the node can not start. - _, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)), headBlock.Root(), false, false, false) + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)), headBlock.Root(), false, false, false) if err != nil { return nil, err // The relevant snapshot(s) might not exist } + + var targetRoot common.Hash + if ctx.NArg() == 1 { + targetRoot, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return nil, err + } + } + + triesInMemory := ctx.GlobalUint64(utils.TriesInMemoryFlag.Name) + // If the target state root is not specified, use the HEAD-(n-1) as the + // target. The reason for picking it is: + // - in most of the normal cases, the related state is available + // - the probability of this layer being reorg is very low + var layers []snapshot.Snapshot + if targetRoot == (common.Hash{}) { + // Retrieve all snapshot layers from the current HEAD. + // In theory there are n difflayers + 1 disk layer present, + // so n diff layers are expected to be returned. + layers = snaptree.Snapshots(headHeader.Root, int(triesInMemory), true) + if len(layers) != int(triesInMemory) { + // Reject if the accumulated diff layers are less than n. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return nil, fmt.Errorf("snapshot not old enough yet: need %d more blocks", int(triesInMemory)-len(layers)) + } + // Use the bottom-most diff layer as the target + targetRoot = layers[len(layers)-1].Root() + } + // Ensure the root is really present. The weak assumption + // is the presence of root can indicate the presence of the + // entire trie. + if blob := rawdb.ReadTrieNode(chaindb, targetRoot); len(blob) == 0 { + // The special case is for clique based networks(rinkeby, goerli + // and some other private networks), it's possible that two + // consecutive blocks will have same root. In this case snapshot + // difflayer won't be created. So HEAD-(n-1) may not paired with + // head-(n-1) layer. Instead the paired layer is higher than the + // bottom-most diff layer. Try to find the bottom-most snapshot + // layer with state available. + // + // Note HEAD is ignored. Usually there is the associated + // state available, but we don't want to use the topmost state + // as the pruning target. + var found bool + for i := len(layers) - 2; i >= 1; i-- { + if blob := rawdb.ReadTrieNode(chaindb, layers[i].Root()); len(blob) != 0 { + targetRoot = layers[i].Root() + found = true + log.Info("Selecting middle-layer as the pruning target", "root", targetRoot, "depth", i) + break + } + } + if !found { + if blob := rawdb.ReadTrieNode(chaindb, snaptree.DiskRoot()); len(blob) != 0 { + targetRoot = snaptree.DiskRoot() + found = true + log.Info("Selecting disk-layer as the pruning target", "root", targetRoot) + } + } + if !found { + if len(layers) > 0 { + return nil, errors.New("no snapshot paired state") + } + return nil, fmt.Errorf("associated state[%x] is not present", targetRoot) + } + } else { + if len(layers) > 0 { + log.Info("Selecting bottom-most difflayer as the pruning target", "root", targetRoot, "height", headHeader.Number.Uint64()-127) + } else { + log.Info("Selecting user-specified state as the pruning target", "root", targetRoot) + } + } + if err := snaptree.Verify(targetRoot); err != nil { + log.Error("Failed to verfiy state", "root", targetRoot, "err", err) + return nil, err + } return chaindb, nil } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 583dd24a00..f213abb0df 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1883,7 +1883,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, args ...bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly ...bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1893,10 +1893,10 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, args ...bool) ethdb.D ) if ctx.GlobalString(SyncModeFlag.Name) == "light" { name := "lightchaindata" - chainDb, err = stack.OpenDatabase(name, cache, handles, "", args[0]) + chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly[0]) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", args...) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly...) } if err != nil { Fatalf("Could not open database: %v", err) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 0fea1ebd32..ed5d6a51a8 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -151,9 +151,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, args ...bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly ...bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, args[0]) + frdb, err := newFreezer(freezer, namespace, readonly[0]) if err != nil { return nil, err } @@ -230,7 +230,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } // Freezer is consistent with the key-value database, permit combining the two - if len(args) == 1 && !frdb.readonly { + if len(readonly) == 1 && !frdb.readonly { go frdb.freeze(db) } return &freezerdb{ @@ -264,12 +264,12 @@ 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, args ...bool) (ethdb.Database, error) { - kvdb, err := leveldb.New(file, cache, handles, namespace, args[0]) +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[0]) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, args...) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly...) if err != nil { kvdb.Close() return nil, err diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 0b85bfc1ed..30ac9b5f5f 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -129,7 +129,6 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie } func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string) (*BlockPruner, error) { - return &BlockPruner{ db: db, oldAncientPath: oldAncientPath, diff --git a/node/node.go b/node/node.go index af1595bccb..2a8b57d427 100644 --- a/node/node.go +++ b/node/node.go @@ -606,7 +606,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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, args ...bool) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly ...bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -625,7 +625,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, args...) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly...) } if err == nil { From cb1ca71b717494b1d587c6b52aa017f805f8ac7d Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Mon, 27 Dec 2021 19:21:46 +0800 Subject: [PATCH 08/26] add one more parameter to indicate blockprune --- cmd/geth/chaincmd.go | 6 +++--- cmd/geth/dbcmd.go | 14 +++++++------- cmd/geth/pruneblock_test.go | 4 ++-- cmd/geth/snapshot.go | 8 ++++---- cmd/utils/flags.go | 10 +++++----- core/blockchain_repair_test.go | 4 ++-- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 4 ++-- core/blockchain_test.go | 18 +++++++++--------- core/rawdb/accessors_chain_test.go | 2 +- core/rawdb/database.go | 12 ++++++------ core/state/pruner/pruner.go | 6 +++--- node/node.go | 6 +++--- 13 files changed, 48 insertions(+), 48 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 1152bfdfdd..a30efbbdea 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -458,7 +458,7 @@ func importPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -477,7 +477,7 @@ func exportPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -491,7 +491,7 @@ func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) for _, arg := range ctx.Args() { var header *types.Header if hashish(arg) { diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 4c70373e9a..1f56069b5c 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -282,7 +282,7 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() return rawdb.InspectDatabase(db, prefix, start) @@ -305,7 +305,7 @@ func dbStats(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() showLeveldbStats(db) @@ -316,7 +316,7 @@ func dbCompact(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() log.Info("Stats before compaction") @@ -340,7 +340,7 @@ func dbGet(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -365,7 +365,7 @@ func dbDelete(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -392,7 +392,7 @@ func dbPut(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() var ( @@ -426,7 +426,7 @@ func dbDumpTrie(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() var ( root []byte diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 40faeb7a40..428dad097f 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -86,7 +86,7 @@ func TestOfflineBlockPrune(t *testing.T) { t.Fatalf("Failed to back up block: %v", err) } - dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false) + dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false, true) if err != nil { t.Fatalf("failed to create database with ancient backend") } @@ -129,7 +129,7 @@ func TestOfflineBlockPrune(t *testing.T) { func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { //create a database with ancient freezer - db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, true) if err != nil { t.Fatalf("failed to create database with ancient backend") } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index d2d22cecfb..c0c7f95f90 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -320,7 +320,7 @@ func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, false) + chaindb := utils.MakeChainDatabase(ctx, stack, false, false) pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name), ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) if err != nil { log.Error("Failed to open snapshot tree", "err", err) @@ -349,7 +349,7 @@ func verifyState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -387,7 +387,7 @@ func traverseState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -477,7 +477,7 @@ func traverseRawState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f213abb0df..994eaab2e6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1764,7 +1764,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DataDirFlag.Name) { // Check if we have an already initialized chain and fall back to // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack, false) // TODO (MariusVanDerWijden) make this read only + chaindb := MakeChainDatabase(ctx, stack, false, false) // TODO (MariusVanDerWijden) make this read only if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } @@ -1883,7 +1883,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly ...bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, isPruneBlock bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1893,10 +1893,10 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly ...bool) eth ) if ctx.GlobalString(SyncModeFlag.Name) == "light" { name := "lightchaindata" - chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly[0]) + chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly...) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, isPruneBlock) } if err != nil { Fatalf("Could not open database: %v", err) @@ -1926,7 +1926,7 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { // MakeChain creates a chain manager from set command line flags. func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) { var err error - chainDb = MakeChainDatabase(ctx, stack, false) // TODO(rjl493456442) support read-only database + chainDb = MakeChainDatabase(ctx, stack, false, false) // TODO(rjl493456442) support read-only database config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) if err != nil { Fatalf("%v", err) diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 5ddb6d2e07..cfdf9d6572 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1762,7 +1762,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -1832,7 +1832,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { db.Close() // Start a new blockchain back up and see where the repait leads us - db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index 6caec36eab..df3481fc78 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1961,7 +1961,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index b61eb741f0..da7c3f444d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -64,7 +64,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -248,7 +248,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { db.Close() // Start a new blockchain back up and see where the repair leads us - newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false) + newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false, false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 6874534817..45a77844f9 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -653,7 +653,7 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -727,7 +727,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false) + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1594,7 +1594,7 @@ func TestBlockchainRecovery(t *testing.T) { } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1651,7 +1651,7 @@ func TestIncompleteAncientReceiptChainInsertion(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1850,7 +1850,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false) + chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2130,7 +2130,7 @@ func TestTransactionIndices(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2158,7 +2158,7 @@ func TestTransactionIndices(t *testing.T) { // Init block chain with external ancients, check all needed indices has been indexed. limit := []uint64{0, 32, 64, 128} for _, l := range limit { - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2178,7 +2178,7 @@ func TestTransactionIndices(t *testing.T) { } // Reconstruct a block chain which only reserves HEAD-64 tx indices - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2257,7 +2257,7 @@ func TestSkipStaleTxIndicesInFastSync(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index ea9dc436cf..b2810abd72 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -440,7 +440,7 @@ func TestAncientStorage(t *testing.T) { } defer os.Remove(frdir) - db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) + db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index ed5d6a51a8..f752252c56 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -151,9 +151,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) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, isPruneBlock bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, readonly[0]) + frdb, err := newFreezer(freezer, namespace, readonly) if err != nil { return nil, err } @@ -230,7 +230,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } // Freezer is consistent with the key-value database, permit combining the two - if len(readonly) == 1 && !frdb.readonly { + if !isPruneBlock && !frdb.readonly { go frdb.freeze(db) } return &freezerdb{ @@ -264,12 +264,12 @@ 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) { - kvdb, err := leveldb.New(file, cache, handles, namespace, readonly[0]) +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly, isPruneBlock 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...) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, isPruneBlock) if err != nil { kvdb.Close() return nil, err diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 30ac9b5f5f..4adeaa0bb9 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -255,8 +255,8 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } -func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, namespace string, args ...bool) ([]*types.Block, []types.Receipts, []*big.Int, error) { - chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, args...) +func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, namespace string, readonly bool) ([]*types.Block, []types.Receipts, []*big.Int, error) { + chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, readonly, true) if err != nil { log.Error("Failed to open ancient database", "err=", err) return nil, nil, nil, err @@ -308,7 +308,7 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespac start := time.Now() - blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly, true) + blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly) if err != nil { return err } diff --git a/node/node.go b/node/node.go index 2a8b57d427..d206da32f8 100644 --- a/node/node.go +++ b/node/node.go @@ -586,7 +586,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di if persistDiff { chainDataHandles = handles * chainDataHandlesPercentage / 100 } - chainDB, err := n.OpenDatabaseWithFreezer(name, cache, chainDataHandles, freezer, namespace, readonly) + chainDB, err := n.OpenDatabaseWithFreezer(name, cache, chainDataHandles, freezer, namespace, readonly, false) if err != nil { return nil, err } @@ -606,7 +606,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly, isPruneBlock bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -625,7 +625,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, isPruneBlock) } if err == nil { From 7b662d5ba58b81fd3646c6700afafb46c8fbf213 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 28 Dec 2021 01:10:31 +0800 Subject: [PATCH 09/26] update the logic of creating freezerDb --- .gitignore | 1 + cmd/geth/main.go | 1 + cmd/geth/pruneblock_test.go | 17 ++++---- cmd/geth/snapshot.go | 4 +- cmd/geth/usage.go | 1 + cmd/utils/flags.go | 5 +++ core/rawdb/database.go | 10 +++++ core/rawdb/freezer.go | 39 ++++++++--------- core/state/pruner/pruner.go | 83 +++++++++++++++++++------------------ 9 files changed, 94 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 25ae6adc8e..0271db9ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ profile.cov **/yarn-error.log cmd/geth/node/ +cmd/geth/__debug_bin diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3e2585fed6..b75b9ee4b9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -165,6 +165,7 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, + utils.BlockPruneQuantity, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 428dad097f..618cfc8ae2 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -78,7 +78,8 @@ func TestOfflineBlockPrune(t *testing.T) { node, _ := startEthService(t, gspec, blocks, chaindbPath) defer node.Close() - testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath) + //Initialize a block pruner for pruning amount of 328 blocks + testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, 328) if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } @@ -93,7 +94,7 @@ func TestOfflineBlockPrune(t *testing.T) { defer dbBack.Close() //check against if the backup data matched original one - for blockNumber := startBlockNumber; blockNumber < startBlockNumber+128; blockNumber++ { + for blockNumber := startBlockNumber; blockNumber < startBlockNumber+328; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber) block := rawdb.ReadBlock(dbBack, blockHash, blockNumber) if reflect.DeepEqual(block, blockList[blockNumber-startBlockNumber]) { @@ -129,7 +130,7 @@ func TestOfflineBlockPrune(t *testing.T) { func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { //create a database with ancient freezer - db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, true) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } @@ -142,8 +143,7 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Dat } // Make chain starting from genesis - - blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 200, func(i int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 500, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{0: byte(canonicalSeed), 19: byte(i)}) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key) if err != nil { @@ -168,11 +168,14 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Dat if err != nil || frozen == 0 { t.Fatalf("Failed to import canonical chain start: %v", err) } + if frozen < 328 { + t.Fatalf("block amount is not enough for pruning: %v", err) + } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(db) // Get the actual start block number. - startBlockNumber := frozen - 128 + oldOffSet - // Initialize the slice to buffer the 128 block data. + startBlockNumber := frozen - 328 + oldOffSet + // Initialize the slice to buffer the block data left. blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber) receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber) externTdList := make([]*big.Int, 0, blockPruneBackUpBlockNumber) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index c0c7f95f90..9bfa54fce7 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -93,6 +93,7 @@ the trie clean cache with default directory will be deleted. Flags: []cli.Flag{ utils.DataDirFlag, utils.AncientFlag, + utils.BlockPruneQuantity, }, Description: ` geth offline prune-block for block data in ancientdb. @@ -271,6 +272,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() + BlockPruneQuantity := ctx.GlobalUint64(utils.BlockPruneQuantity.Name) chaindb, err := accessDb(ctx, stack) if err != nil { return err @@ -293,7 +295,7 @@ func pruneBlock(ctx *cli.Context) error { return err } - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath) + blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockPruneQuantity) if err != nil { return err } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index fba14530b5..3e489a7c1b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -58,6 +58,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LightKDFFlag, utils.WhitelistFlag, utils.TriesInMemoryFlag, + utils.BlockPruneQuantity, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 994eaab2e6..aa3ac46fd8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -827,6 +827,11 @@ var ( Name: "catalyst", Usage: "Catalyst mode (eth2 integration testing)", } + + BlockPruneQuantity = cli.Uint64Flag{ + Name: "blockprune-quantity", + Usage: "Sets the quantity of blocks for offline block prune", + } ) // MakeDataDir retrieves the currently requested data directory, terminating diff --git a/core/rawdb/database.go b/core/rawdb/database.go index f752252c56..f9471bceb9 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -148,6 +148,16 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { } } +//NewFreezerDb only create a freezer without statedb. +func NewFreezerDb(freezer, namespace string, readonly bool) (*freezer, error) { + // Create the idle freezer instance + frdb, err := newFreezer(freezer, namespace, readonly) + if err != nil { + return nil, err + } + return frdb, nil +} + // 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. diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 14f14f98ff..209fddcb41 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -142,6 +142,11 @@ func newFreezer(datadir string, namespace string, readonly bool) (*freezer, erro return freezer, nil } +func (f *freezer) SetOffSet(offset uint64) error { + f.offset = offset + return nil +} + // Close terminates the chain freezer, unmapping all the data files. func (f *freezer) Close() error { var errs []error @@ -316,10 +321,6 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { } number := ReadHeaderNumber(nfdb, hash) - //minus the freezer offset - if number != nil { - *number = *number - f.offset - } threshold := atomic.LoadUint64(&f.threshold) switch { @@ -346,44 +347,44 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { } // Seems we have data ready to be frozen, process in usable batches limit := *number - threshold - if limit-f.frozen > freezerBatchLimit { - limit = f.frozen + freezerBatchLimit + if limit-f.frozen-f.offset > freezerBatchLimit { + limit = f.frozen + f.offset + freezerBatchLimit } var ( start = time.Now() - first = f.frozen - ancients = make([]common.Hash, 0, limit-f.frozen) + first = f.frozen + f.offset + ancients = make([]common.Hash, 0, limit-f.frozen-f.offset) ) - for f.frozen <= limit { + for f.frozen+f.offset <= limit { // Retrieves all the components of the canonical block - hash := ReadCanonicalHash(nfdb, f.frozen) + hash := ReadCanonicalHash(nfdb, f.frozen+f.offset) if hash == (common.Hash{}) { log.Error("Canonical hash missing, can't freeze", "number", f.frozen) break } - header := ReadHeaderRLP(nfdb, hash, f.frozen) + header := ReadHeaderRLP(nfdb, hash, f.frozen+f.offset) if len(header) == 0 { log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash) break } - body := ReadBodyRLP(nfdb, hash, f.frozen) + body := ReadBodyRLP(nfdb, hash, f.frozen+f.offset) if len(body) == 0 { log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash) break } - receipts := ReadReceiptsRLP(nfdb, hash, f.frozen) + receipts := ReadReceiptsRLP(nfdb, hash, f.frozen+f.offset) if len(receipts) == 0 { log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash) break } - td := ReadTdRLP(nfdb, hash, f.frozen) + td := ReadTdRLP(nfdb, hash, f.frozen+f.offset) if len(td) == 0 { log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash) break } log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash) // Inject all the components into the relevant data tables - if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil { + if err := f.AppendAncient(f.frozen+f.offset, hash[:], header, body, receipts, td); err != nil { break } ancients = append(ancients, hash) @@ -408,7 +409,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { // Wipe out side chains also and track dangling side chians var dangling []common.Hash - for number := first; number < f.frozen; number++ { + for number := first; number < f.frozen+f.offset; number++ { // Always keep the genesis block in active database if number != 0 { dangling = ReadAllHashes(db, number) @@ -424,8 +425,8 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { batch.Reset() // Step into the future and delete and dangling side chains - if f.frozen > 0 { - tip := f.frozen + if f.frozen+f.offset > 0 { + tip := f.frozen + f.offset for len(dangling) > 0 { drop := make(map[common.Hash]struct{}) for _, hash := range dangling { @@ -466,7 +467,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { log.Info("Deep froze chain segment", context...) // Avoid database thrashing with tiny writes - if f.frozen-first < freezerBatchLimit { + if f.frozen+f.offset-first < freezerBatchLimit { backoff = true } } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 4adeaa0bb9..83450d3e98 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -57,9 +57,6 @@ const ( // triggering range compaction. It's a quite arbitrary number but just // to avoid triggering range compaction because of small deletion. rangeCompactionThreshold = 100000 - - // back up the most recent 128 blocks in ancientdb - blockPruneBackUpBlockNumber = 128 ) var ( @@ -92,10 +89,11 @@ type Pruner struct { } type BlockPruner struct { - db ethdb.Database - oldAncientPath string - newAncientPath string - node *node.Node + db ethdb.Database + oldAncientPath string + newAncientPath string + node *node.Node + BlockPruneQuantity uint64 } // NewPruner creates the pruner instance. @@ -128,12 +126,13 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockPruneQuantity uint64) (*BlockPruner, error) { return &BlockPruner{ - db: db, - oldAncientPath: oldAncientPath, - newAncientPath: newAncientPath, - node: n, + db: db, + oldAncientPath: oldAncientPath, + newAncientPath: newAncientPath, + node: n, + BlockPruneQuantity: BlockPruneQuantity, }, nil } @@ -255,52 +254,61 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } -func (p *BlockPruner) backUpOldDb(name string, cache, handles int, freezer, namespace string, readonly bool) ([]*types.Block, []types.Receipts, []*big.Int, error) { - chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, readonly, true) +func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace string, readonly bool) error { + chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.oldAncientPath, namespace, readonly, true) if err != nil { log.Error("Failed to open ancient database", "err=", err) - return nil, nil, nil, err + return err } defer chainDb.Close() + //Create new ancientdb backup. + frdbBack, err := rawdb.NewFreezerDb(p.newAncientPath, namespace, readonly) + if err != nil { + log.Error("Failed to create ancient freezer backup", "err=", err) + return err + } + defer frdbBack.Close() + // Get the number of items in ancient db, frozen it is. frozen, err := chainDb.Ancients() - // Write the latest 128 blocks data of the ancient db + // If we can't access the freezer or it's empty, abort. if err != nil || frozen == 0 { log.Error("can't access the freezer or it's empty, abort") - return nil, nil, nil, errors.New("can't access the freezer or it's empty, abort") + return errors.New("can't access the freezer or it's empty, abort") + } + //If the items in freezer is less than the block amount that we want to prune, it is not enough, should stop. + if frozen < p.BlockPruneQuantity { + log.Error("the number of old blocks is not enough to prune, should specify lower BlockPruneQuantity") + return errors.New("the number of old blocks is not enough to prune") } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) // Get the actual start block number. - startBlockNumber := frozen - blockPruneBackUpBlockNumber + oldOffSet + startBlockNumber := oldOffSet + frozen - p.BlockPruneQuantity + // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. + frdbBack.SetOffSet(startBlockNumber) + // Write the new offset into statedb for the future new freezer usage. + rawdb.WriteOffSetOfAncientFreezer(chainDb, startBlockNumber) - // Initialize the slice to buffer the 128 block data. - blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber) - receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber) - externTdList := make([]*big.Int, 0, blockPruneBackUpBlockNumber) - // All ancient data within the most recent 128 blocks write into memory buffer for future new ancient_back directory usage. + // All ancient data after and including startBlockNumber should write into new ancient_back. for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) - blockList = append(blockList, block) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) - receiptsList = append(receiptsList, receipts) // Calculate the total difficulty of the block td := rawdb.ReadTd(chainDb, blockHash, blockNumber) if td == nil { - return nil, nil, nil, consensus.ErrUnknownAncestor + return consensus.ErrUnknownAncestor } externTd := new(big.Int).Add(block.Difficulty(), td) - externTdList = append(externTdList, externTd) + + //Write into new ancient_back db. + rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) } - // For every round, newoffset actually equals to the startBlockNumber in ancient db. - newOffSet := oldOffSet + frozen - 128 - // Write the new offset into db for the future new freezer usage. - rawdb.WriteOffSetOfAncientFreezer(chainDb, newOffSet) - return blockList, receiptsList, externTdList, nil + return nil } // Backup the ancient data for the old ancient db, i.e. the most recent 128 blocks in ancient db. @@ -308,23 +316,18 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespac start := time.Now() - blockList, receiptsList, externTdList, err := p.backUpOldDb(name, cache, handles, p.oldAncientPath, namespace, readonly) - if err != nil { + if err := p.backUpOldDb(name, cache, handles, namespace, readonly); err != nil { return err } - // Create new freezer backup directory backFreezer, in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup. + // Open the new freezer backup directory backFreezer for assemble with statedb together, + //in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup. chainDbBack, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.newAncientPath, namespace, readonly, true) if err != nil { return err } defer chainDbBack.Close() - // Write into ancient_backup - for id := 0; id < len(blockList); id++ { - rawdb.WriteAncientBlock(chainDbBack, blockList[id], receiptsList[id], externTdList[id]) - } - log.Info("Block pruning BackUp successfully", "time duration since start is", common.PrettyDuration(time.Since(start))) return nil } From eb1263d7d333a9883baa0ebd1a0f9bea03c560fd Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 28 Dec 2021 12:55:08 +0800 Subject: [PATCH 10/26] update flag command description --- cmd/geth/main.go | 2 +- cmd/geth/snapshot.go | 18 ++++++++++-------- cmd/geth/usage.go | 2 +- cmd/utils/flags.go | 6 +++--- core/state/pruner/pruner.go | 28 ++++++++++++++-------------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b75b9ee4b9..515ea33029 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -165,7 +165,7 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, - utils.BlockPruneQuantity, + utils.BlockPruneAmountLeft, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 9bfa54fce7..e6da45fe40 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -93,16 +93,18 @@ the trie clean cache with default directory will be deleted. Flags: []cli.Flag{ utils.DataDirFlag, utils.AncientFlag, - utils.BlockPruneQuantity, + utils.BlockPruneAmountLeft, }, Description: ` geth offline prune-block for block data in ancientdb. -will prune all the old block data in ancientdb except for the the last 128 blocks in ancientdb. -the brief workflow is to backup the last 128 blocks in original ancientdb into new ancient_backup, -then delete the original ancientdb dir and rename the ancient_backup to original one for replacement. +The amount of blocks expected for remaining after prune can be specified via blockprune-amount in this command, +will prune and only remain the specified amount of old block data in ancientdb. +the brief workflow is to backup the the number of this specified amount blocks backward in original ancientdb +into new ancient_backup, then delete the original ancientdb dir and rename the ancient_backup to original one for replacement, +finally assemble the statedb and new ancientDb together. The purpose of doing it is because the block data will be moved into the ancient store when it -becomes old enough(exceed the Threshold 90000), the disk usage will be very large over time, so it's very -necessary to do block data prune, this feature takes very short time in even seconds level. +becomes old enough(exceed the Threshold 90000), the disk usage will be very large over time, and is occupied mainly by ancientDb, +so it's very necessary to do block data prune, this feature takes very short time in even seconds level. `, }, { @@ -272,7 +274,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - BlockPruneQuantity := ctx.GlobalUint64(utils.BlockPruneQuantity.Name) + BlockPruneAmountLeft := ctx.GlobalUint64(utils.BlockPruneAmountLeft.Name) chaindb, err := accessDb(ctx, stack) if err != nil { return err @@ -295,7 +297,7 @@ func pruneBlock(ctx *cli.Context) error { return err } - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockPruneQuantity) + blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockPruneAmountLeft) if err != nil { return err } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 3e489a7c1b..913da9f420 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -58,7 +58,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LightKDFFlag, utils.WhitelistFlag, utils.TriesInMemoryFlag, - utils.BlockPruneQuantity, + utils.BlockPruneAmountLeft, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index aa3ac46fd8..2be64420fc 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -828,9 +828,9 @@ var ( Usage: "Catalyst mode (eth2 integration testing)", } - BlockPruneQuantity = cli.Uint64Flag{ - Name: "blockprune-quantity", - Usage: "Sets the quantity of blocks for offline block prune", + BlockPruneAmountLeft = cli.Uint64Flag{ + Name: "blockprune-amount", + Usage: "Sets the expected remained amount of blocks for offline block prune", } ) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 83450d3e98..7bb2f1dfbe 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -89,11 +89,11 @@ type Pruner struct { } type BlockPruner struct { - db ethdb.Database - oldAncientPath string - newAncientPath string - node *node.Node - BlockPruneQuantity uint64 + db ethdb.Database + oldAncientPath string + newAncientPath string + node *node.Node + BlockPruneAmountLeft uint64 } // NewPruner creates the pruner instance. @@ -126,13 +126,13 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockPruneQuantity uint64) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockPruneAmountLeft uint64) (*BlockPruner, error) { return &BlockPruner{ - db: db, - oldAncientPath: oldAncientPath, - newAncientPath: newAncientPath, - node: n, - BlockPruneQuantity: BlockPruneQuantity, + db: db, + oldAncientPath: oldAncientPath, + newAncientPath: newAncientPath, + node: n, + BlockPruneAmountLeft: BlockPruneAmountLeft, }, nil } @@ -279,14 +279,14 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str return errors.New("can't access the freezer or it's empty, abort") } //If the items in freezer is less than the block amount that we want to prune, it is not enough, should stop. - if frozen < p.BlockPruneQuantity { - log.Error("the number of old blocks is not enough to prune, should specify lower BlockPruneQuantity") + if frozen < p.BlockPruneAmountLeft { + log.Error("the number of old blocks is not enough to prune, should specify larger BlockPruneAmountLeft for remaining more blocks and prune less blocks") return errors.New("the number of old blocks is not enough to prune") } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) // Get the actual start block number. - startBlockNumber := oldOffSet + frozen - p.BlockPruneQuantity + startBlockNumber := oldOffSet + frozen - p.BlockPruneAmountLeft // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. frdbBack.SetOffSet(startBlockNumber) // Write the new offset into statedb for the future new freezer usage. From d1fb290cf4813963784e94061135166ff98c34c6 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 28 Dec 2021 15:39:22 +0800 Subject: [PATCH 11/26] expose the function for db inspect the offset/startBlockNumber --- cmd/geth/dbcmd.go | 2 +- core/rawdb/database.go | 3 +++ core/state/pruner/pruner.go | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 1f56069b5c..3b6ec4ce53 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -282,7 +282,7 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, true) defer db.Close() return rawdb.InspectDatabase(db, prefix, start) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index f9471bceb9..d8fdc3fd8a 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -320,6 +320,7 @@ func (s *stat) Count() string { // InspectDatabase traverses the entire database and checks the size // of all different categories of data. func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { + offset := counter(ReadOffSetOfAncientFreezer(db)) it := db.NewIterator(keyPrefix, keyStart) defer it.Release() @@ -452,6 +453,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { if count, err := db.Ancients(); err == nil { ancients = counter(count) } + // Display the database statistic. stats := [][]string{ {"Key-Value store", "Headers", headers.Size(), headers.Count()}, @@ -478,6 +480,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, + {"Offset/StartBlockNumber", "Offset of AncientStore", "", offset.String()}, } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Size", "Items"}) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 7bb2f1dfbe..dfc73d84e8 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -285,7 +285,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) - // Get the actual start block number. + // Get the start BlockNumber for pruning. startBlockNumber := oldOffSet + frozen - p.BlockPruneAmountLeft // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. frdbBack.SetOffSet(startBlockNumber) From f532e378d127205c7b21d5ce5bf908c9f0a0bd71 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 28 Dec 2021 16:16:21 +0800 Subject: [PATCH 12/26] add flags to inspect prune info --- cmd/geth/dbcmd.go | 22 +++++++++++++++++++++- cmd/geth/pruneblock_test.go | 21 ++++++++++++++------- cmd/geth/snapshot.go | 2 +- cmd/utils/flags.go | 2 +- core/rawdb/database.go | 21 +++++++++++++++++++-- 5 files changed, 56 insertions(+), 12 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 3b6ec4ce53..fa35234b3d 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -62,6 +62,7 @@ Remove blockchain and state databases`, dbPutCmd, dbGetSlotsCmd, dbDumpFreezerIndex, + blockPruneInspectCmd, }, } dbInspectCmd = cli.Command{ @@ -195,6 +196,16 @@ WARNING: This is a low-level operation which may cause database corruption!`, }, Description: "This command displays information about the freezer index.", } + blockPruneInspectCmd = cli.Command{ + Action: utils.MigrateFlags(blockPruneInspect), + Name: "inspect-reserved-oldest-blocks", + Flags: []cli.Flag{ + utils.DataDirFlag, + }, + Usage: "Inspect the ancientStore information after block-prune", + Description: `This commands will read current offset from kvdb, which is the current offset and starting BlockNumber + of ancientStore, will also displays the reserved number of blocks in ancientStore `, + } ) func removeDB(ctx *cli.Context) error { @@ -282,12 +293,21 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, true) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() return rawdb.InspectDatabase(db, prefix, start) } +func blockPruneInspect(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true, true) + defer db.Close() + return rawdb.InspectBlockPrune(db) +} + func showLeveldbStats(db ethdb.Stater) { if stats, err := db.Stat("leveldb.stats"); err != nil { log.Warn("Failed to read database stats", "error", err) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 618cfc8ae2..c431fc4534 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -64,6 +64,13 @@ var ( ) func TestOfflineBlockPrune(t *testing.T) { + //Corner case for 0 remain in ancinetStore. + testOfflineBlockPruneWithAmountLeft(t, 0) + //General case. + testOfflineBlockPruneWithAmountLeft(t, 100) +} + +func testOfflineBlockPruneWithAmountLeft(t *testing.T, amountLeft uint64) { datadir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Failed to create temporary datadir: %v", err) @@ -74,12 +81,12 @@ func TestOfflineBlockPrune(t *testing.T) { oldAncientPath := filepath.Join(chaindbPath, "ancient") newAncientPath := filepath.Join(chaindbPath, "ancient_back") - db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath) + db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath, amountLeft) node, _ := startEthService(t, gspec, blocks, chaindbPath) defer node.Close() - //Initialize a block pruner for pruning amount of 328 blocks - testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, 328) + //Initialize a block pruner for pruning, only remain amountLeft blocks backward. + testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountLeft) if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } @@ -94,7 +101,7 @@ func TestOfflineBlockPrune(t *testing.T) { defer dbBack.Close() //check against if the backup data matched original one - for blockNumber := startBlockNumber; blockNumber < startBlockNumber+328; blockNumber++ { + for blockNumber := startBlockNumber; blockNumber < startBlockNumber+amountLeft; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber) block := rawdb.ReadBlock(dbBack, blockHash, blockNumber) if reflect.DeepEqual(block, blockList[blockNumber-startBlockNumber]) { @@ -128,7 +135,7 @@ func TestOfflineBlockPrune(t *testing.T) { } } -func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { +func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemain uint64) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { //create a database with ancient freezer db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, false) if err != nil { @@ -168,13 +175,13 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string) (ethdb.Dat if err != nil || frozen == 0 { t.Fatalf("Failed to import canonical chain start: %v", err) } - if frozen < 328 { + if frozen < blockRemain { t.Fatalf("block amount is not enough for pruning: %v", err) } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(db) // Get the actual start block number. - startBlockNumber := frozen - 328 + oldOffSet + startBlockNumber := frozen - blockRemain + oldOffSet // Initialize the slice to buffer the block data left. blockList := make([]*types.Block, 0, blockPruneBackUpBlockNumber) receiptsList := make([]types.Receipts, 0, blockPruneBackUpBlockNumber) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index e6da45fe40..7f8cdfc1ab 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -97,7 +97,7 @@ the trie clean cache with default directory will be deleted. }, Description: ` geth offline prune-block for block data in ancientdb. -The amount of blocks expected for remaining after prune can be specified via blockprune-amount in this command, +The amount of blocks expected for remaining after prune can be specified via reserved-recent-blocks in this command, will prune and only remain the specified amount of old block data in ancientdb. the brief workflow is to backup the the number of this specified amount blocks backward in original ancientdb into new ancient_backup, then delete the original ancientdb dir and rename the ancient_backup to original one for replacement, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2be64420fc..4822c3c75a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -829,7 +829,7 @@ var ( } BlockPruneAmountLeft = cli.Uint64Flag{ - Name: "blockprune-amount", + Name: "reserved-recent-blocks", Usage: "Sets the expected remained amount of blocks for offline block prune", } ) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index d8fdc3fd8a..4a85fef2e6 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -316,11 +316,29 @@ func (s *stat) Size() string { func (s *stat) Count() string { return s.count.String() } +func InspectBlockPrune(db ethdb.Database) error { + offset := counter(ReadOffSetOfAncientFreezer(db)) + // Get number of ancient rows inside the freezer + ancients := counter(0) + if count, err := db.Ancients(); err == nil { + ancients = counter(count) + } + stats := [][]string{ + {"Offset/StartBlockNumber", "Offset/StartBlockNumber after BlockPrune", offset.String()}, + {"Amount of remained items in AncientStore", "remaining items after BlockPrune", ancients.String()}, + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Database", "Category", "Items"}) + table.SetFooter([]string{"", "AncientStore information after offline BlockPrune", ""}) + table.AppendBulk(stats) + table.Render() + + return nil +} // InspectDatabase traverses the entire database and checks the size // of all different categories of data. func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { - offset := counter(ReadOffSetOfAncientFreezer(db)) it := db.NewIterator(keyPrefix, keyStart) defer it.Release() @@ -480,7 +498,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, - {"Offset/StartBlockNumber", "Offset of AncientStore", "", offset.String()}, } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Size", "Items"}) From c14e873c6b78ff23451dda9d0fa334e0f541b935 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Wed, 29 Dec 2021 18:51:36 +0800 Subject: [PATCH 13/26] rename flag of reserved-recent-blocks to block-amount-reserved --- cmd/geth/snapshot.go | 2 +- cmd/utils/flags.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 7f8cdfc1ab..7f515bcf9b 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -97,7 +97,7 @@ the trie clean cache with default directory will be deleted. }, Description: ` geth offline prune-block for block data in ancientdb. -The amount of blocks expected for remaining after prune can be specified via reserved-recent-blocks in this command, +The amount of blocks expected for remaining after prune can be specified via block-amount-reserved in this command, will prune and only remain the specified amount of old block data in ancientdb. the brief workflow is to backup the the number of this specified amount blocks backward in original ancientdb into new ancient_backup, then delete the original ancientdb dir and rename the ancient_backup to original one for replacement, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4822c3c75a..fdc1877e41 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -829,7 +829,7 @@ var ( } BlockPruneAmountLeft = cli.Uint64Flag{ - Name: "reserved-recent-blocks", + Name: "block-amount-reserved", Usage: "Sets the expected remained amount of blocks for offline block prune", } ) From aaaee6b384eca22a6a0f3245f8aed66cf7877b2f Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 30 Dec 2021 04:13:35 +0800 Subject: [PATCH 14/26] addressed comments from walt --- cmd/geth/dbcmd.go | 12 ++++----- cmd/geth/main.go | 2 +- cmd/geth/pruneblock_test.go | 14 +++++----- cmd/geth/snapshot.go | 53 +++++++++++++++---------------------- cmd/geth/usage.go | 2 +- cmd/utils/flags.go | 6 ++--- core/rawdb/database.go | 12 ++++----- core/state/pruner/pruner.go | 36 ++++++++++++++----------- node/node.go | 4 +-- 9 files changed, 68 insertions(+), 73 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index fa35234b3d..8c7ac6226e 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -62,7 +62,7 @@ Remove blockchain and state databases`, dbPutCmd, dbGetSlotsCmd, dbDumpFreezerIndex, - blockPruneInspectCmd, + ancientInspectCmd, }, } dbInspectCmd = cli.Command{ @@ -196,13 +196,13 @@ WARNING: This is a low-level operation which may cause database corruption!`, }, Description: "This command displays information about the freezer index.", } - blockPruneInspectCmd = cli.Command{ - Action: utils.MigrateFlags(blockPruneInspect), + ancientInspectCmd = cli.Command{ + Action: utils.MigrateFlags(ancientInspect), Name: "inspect-reserved-oldest-blocks", Flags: []cli.Flag{ utils.DataDirFlag, }, - Usage: "Inspect the ancientStore information after block-prune", + Usage: "Inspect the ancientStore information", Description: `This commands will read current offset from kvdb, which is the current offset and starting BlockNumber of ancientStore, will also displays the reserved number of blocks in ancientStore `, } @@ -299,13 +299,13 @@ func inspect(ctx *cli.Context) error { return rawdb.InspectDatabase(db, prefix, start) } -func blockPruneInspect(ctx *cli.Context) error { +func ancientInspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack, true, true) defer db.Close() - return rawdb.InspectBlockPrune(db) + return rawdb.AncientInspect(db) } func showLeveldbStats(db ethdb.Stater) { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 515ea33029..0ad7dba734 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -165,7 +165,7 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, - utils.BlockPruneAmountLeft, + utils.BlockAmountReserved, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index c431fc4534..680fc69e77 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -65,12 +65,12 @@ var ( func TestOfflineBlockPrune(t *testing.T) { //Corner case for 0 remain in ancinetStore. - testOfflineBlockPruneWithAmountLeft(t, 0) + testOfflineBlockPruneWithAmountReserved(t, 0) //General case. - testOfflineBlockPruneWithAmountLeft(t, 100) + testOfflineBlockPruneWithAmountReserved(t, 100) } -func testOfflineBlockPruneWithAmountLeft(t *testing.T, amountLeft uint64) { +func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64) { datadir, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Failed to create temporary datadir: %v", err) @@ -81,12 +81,12 @@ func testOfflineBlockPruneWithAmountLeft(t *testing.T, amountLeft uint64) { oldAncientPath := filepath.Join(chaindbPath, "ancient") newAncientPath := filepath.Join(chaindbPath, "ancient_back") - db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath, amountLeft) + db, blocks, blockList, receiptsList, externTdList, startBlockNumber, _ := BlockchainCreator(t, chaindbPath, oldAncientPath, amountReserved) node, _ := startEthService(t, gspec, blocks, chaindbPath) defer node.Close() - //Initialize a block pruner for pruning, only remain amountLeft blocks backward. - testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountLeft) + //Initialize a block pruner for pruning, only remain amountReserved blocks backward. + testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountReserved) if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } @@ -101,7 +101,7 @@ func testOfflineBlockPruneWithAmountLeft(t *testing.T, amountLeft uint64) { defer dbBack.Close() //check against if the backup data matched original one - for blockNumber := startBlockNumber; blockNumber < startBlockNumber+amountLeft; blockNumber++ { + for blockNumber := startBlockNumber; blockNumber < startBlockNumber+amountReserved; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber) block := rawdb.ReadBlock(dbBack, blockHash, blockNumber) if reflect.DeepEqual(block, blockList[blockNumber-startBlockNumber]) { diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 7f515bcf9b..bc6eae9995 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -45,6 +45,9 @@ var ( // emptyCode is the known hash of the empty EVM bytecode. emptyCode = crypto.Keccak256(nil) + + //the layer of tries trees that keep in memory + TriesInMemory = 128 ) var ( @@ -93,7 +96,7 @@ the trie clean cache with default directory will be deleted. Flags: []cli.Flag{ utils.DataDirFlag, utils.AncientFlag, - utils.BlockPruneAmountLeft, + utils.BlockAmountReserved, }, Description: ` geth offline prune-block for block data in ancientdb. @@ -104,7 +107,7 @@ into new ancient_backup, then delete the original ancientdb dir and rename the a finally assemble the statedb and new ancientDb together. The purpose of doing it is because the block data will be moved into the ancient store when it becomes old enough(exceed the Threshold 90000), the disk usage will be very large over time, and is occupied mainly by ancientDb, -so it's very necessary to do block data prune, this feature takes very short time in even seconds level. +so it's very necessary to do block data prune, this feature will handle it. `, }, { @@ -186,40 +189,28 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { } headHeader := headBlock.Header() //Make sure the MPT and snapshot matches before pruning, otherwise the node can not start. - snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)), headBlock.Root(), false, false, false) + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, TriesInMemory, headBlock.Root(), false, false, false) if err != nil { return nil, err // The relevant snapshot(s) might not exist } - var targetRoot common.Hash - if ctx.NArg() == 1 { - targetRoot, err = parseRoot(ctx.Args()[0]) - if err != nil { - log.Error("Failed to resolve state root", "err", err) - return nil, err - } - } - - triesInMemory := ctx.GlobalUint64(utils.TriesInMemoryFlag.Name) - // If the target state root is not specified, use the HEAD-(n-1) as the - // target. The reason for picking it is: + // Use the HEAD-(n-1) as the target root. The reason for picking it is: // - in most of the normal cases, the related state is available // - the probability of this layer being reorg is very low - var layers []snapshot.Snapshot - if targetRoot == (common.Hash{}) { - // Retrieve all snapshot layers from the current HEAD. - // In theory there are n difflayers + 1 disk layer present, - // so n diff layers are expected to be returned. - layers = snaptree.Snapshots(headHeader.Root, int(triesInMemory), true) - if len(layers) != int(triesInMemory) { - // Reject if the accumulated diff layers are less than n. It - // means in most of normal cases, there is no associated state - // with bottom-most diff layer. - return nil, fmt.Errorf("snapshot not old enough yet: need %d more blocks", int(triesInMemory)-len(layers)) - } - // Use the bottom-most diff layer as the target - targetRoot = layers[len(layers)-1].Root() + + // Retrieve all snapshot layers from the current HEAD. + // In theory there are n difflayers + 1 disk layer present, + // so n diff layers are expected to be returned. + layers := snaptree.Snapshots(headHeader.Root, TriesInMemory, true) + if len(layers) != TriesInMemory { + // Reject if the accumulated diff layers are less than n. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return nil, fmt.Errorf("snapshot not old enough yet: need %d more blocks", TriesInMemory-len(layers)) } + // Use the bottom-most diff layer as the target + targetRoot := layers[len(layers)-1].Root() + // Ensure the root is really present. The weak assumption // is the presence of root can indicate the presence of the // entire trie. @@ -274,7 +265,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - BlockPruneAmountLeft := ctx.GlobalUint64(utils.BlockPruneAmountLeft.Name) + BlockAmountReserved := ctx.GlobalUint64(utils.BlockAmountReserved.Name) chaindb, err := accessDb(ctx, stack) if err != nil { return err @@ -297,7 +288,7 @@ func pruneBlock(ctx *cli.Context) error { return err } - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockPruneAmountLeft) + blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockAmountReserved) if err != nil { return err } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 913da9f420..f42d40ec01 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -58,7 +58,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LightKDFFlag, utils.WhitelistFlag, utils.TriesInMemoryFlag, - utils.BlockPruneAmountLeft, + utils.BlockAmountReserved, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fdc1877e41..ef710056c6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -828,7 +828,7 @@ var ( Usage: "Catalyst mode (eth2 integration testing)", } - BlockPruneAmountLeft = cli.Uint64Flag{ + BlockAmountReserved = cli.Uint64Flag{ Name: "block-amount-reserved", Usage: "Sets the expected remained amount of blocks for offline block prune", } @@ -1888,7 +1888,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, isPruneBlock bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disablFreeze bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1901,7 +1901,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, isPruneBloc chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, isPruneBlock) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disablFreeze) } if err != nil { Fatalf("Could not open database: %v", err) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 4a85fef2e6..e42b7ee5a9 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -161,7 +161,7 @@ func NewFreezerDb(freezer, namespace string, readonly bool) (*freezer, error) { // 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, isPruneBlock bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, disablFreeze bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newFreezer(freezer, namespace, readonly) if err != nil { @@ -240,7 +240,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } // Freezer is consistent with the key-value database, permit combining the two - if !isPruneBlock && !frdb.readonly { + if !disablFreeze && !frdb.readonly { go frdb.freeze(db) } return &freezerdb{ @@ -274,12 +274,12 @@ 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, isPruneBlock bool) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly, disablFreeze 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, isPruneBlock) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, disablFreeze) if err != nil { kvdb.Close() return nil, err @@ -316,7 +316,7 @@ func (s *stat) Size() string { func (s *stat) Count() string { return s.count.String() } -func InspectBlockPrune(db ethdb.Database) error { +func AncientInspect(db ethdb.Database) error { offset := counter(ReadOffSetOfAncientFreezer(db)) // Get number of ancient rows inside the freezer ancients := counter(0) @@ -329,7 +329,7 @@ func InspectBlockPrune(db ethdb.Database) error { } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Items"}) - table.SetFooter([]string{"", "AncientStore information after offline BlockPrune", ""}) + table.SetFooter([]string{"", "AncientStore information", ""}) table.AppendBulk(stats) table.Render() diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index dfc73d84e8..8a2b3b0414 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -89,11 +89,11 @@ type Pruner struct { } type BlockPruner struct { - db ethdb.Database - oldAncientPath string - newAncientPath string - node *node.Node - BlockPruneAmountLeft uint64 + db ethdb.Database + oldAncientPath string + newAncientPath string + node *node.Node + BlockAmountReserved uint64 } // NewPruner creates the pruner instance. @@ -126,13 +126,13 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockPruneAmountLeft uint64) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockAmountReserved uint64) (*BlockPruner, error) { return &BlockPruner{ - db: db, - oldAncientPath: oldAncientPath, - newAncientPath: newAncientPath, - node: n, - BlockPruneAmountLeft: BlockPruneAmountLeft, + db: db, + oldAncientPath: oldAncientPath, + newAncientPath: newAncientPath, + node: n, + BlockAmountReserved: BlockAmountReserved, }, nil } @@ -278,20 +278,23 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str log.Error("can't access the freezer or it's empty, abort") return errors.New("can't access the freezer or it's empty, abort") } - //If the items in freezer is less than the block amount that we want to prune, it is not enough, should stop. - if frozen < p.BlockPruneAmountLeft { - log.Error("the number of old blocks is not enough to prune, should specify larger BlockPruneAmountLeft for remaining more blocks and prune less blocks") - return errors.New("the number of old blocks is not enough to prune") + //If the items in freezer is less than the block amount that we want to reserve, it is not enough, should stop. + if frozen < p.BlockAmountReserved { + log.Error("the number of old blocks is not enough to reserve,", "frozen items", frozen, "the amount specified", p.BlockAmountReserved) + return errors.New("the number of old blocks is not enough to reserve") } oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) // Get the start BlockNumber for pruning. - startBlockNumber := oldOffSet + frozen - p.BlockPruneAmountLeft + startBlockNumber := oldOffSet + frozen - p.BlockAmountReserved // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. frdbBack.SetOffSet(startBlockNumber) // Write the new offset into statedb for the future new freezer usage. rawdb.WriteOffSetOfAncientFreezer(chainDb, startBlockNumber) + log.Info("prune info", "old offset", oldOffSet, "frozen items number", frozen, "amount to reserver", p.BlockAmountReserved) + log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) + // All ancient data after and including startBlockNumber should write into new ancient_back. for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) @@ -306,6 +309,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str //Write into new ancient_back db. rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) + log.Info("backup blockNumber ", "blockNumber", blockNumber) } return nil diff --git a/node/node.go b/node/node.go index d206da32f8..2849cc6ac7 100644 --- a/node/node.go +++ b/node/node.go @@ -606,7 +606,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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, isPruneBlock bool) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly, disablFreeze bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -625,7 +625,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, isPruneBlock) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, disablFreeze) } if err == nil { From a2ed56c181cdadb5b42bca8b998dfa3b5532b114 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Fri, 31 Dec 2021 16:39:30 +0800 Subject: [PATCH 15/26] handle the case of command interruption --- cmd/geth/pruneblock_test.go | 4 +- cmd/geth/snapshot.go | 31 +++++++++-- core/rawdb/accessors_chain.go | 14 ----- core/rawdb/database.go | 77 +++++++++++++++++++++++--- core/rawdb/freezer.go | 5 -- core/rawdb/schema.go | 7 ++- core/state/pruner/pruner.go | 101 ++++++++++++++++++++++++++-------- 7 files changed, 181 insertions(+), 58 deletions(-) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 680fc69e77..c64d13d925 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -90,7 +90,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } - if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false); err != nil { + if err := testBlockPruner.BlockPruneBackUp(chaindbPath, 512, utils.MakeDatabaseHandles(), "", false, false); err != nil { t.Fatalf("Failed to back up block: %v", err) } @@ -179,7 +179,7 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemai t.Fatalf("block amount is not enough for pruning: %v", err) } - oldOffSet := rawdb.ReadOffSetOfAncientFreezer(db) + oldOffSet := rawdb.ReadOffSetOfCurrentAncientFreezer(db) // Get the actual start block number. startBlockNumber := frozen - blockRemain + oldOffSet // Initialize the slice to buffer the block data left. diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index bc6eae9995..340d803c9a 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -20,6 +20,7 @@ import ( "bytes" "errors" "fmt" + "os" "path/filepath" "time" @@ -265,7 +266,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { func pruneBlock(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - BlockAmountReserved := ctx.GlobalUint64(utils.BlockAmountReserved.Name) + blockAmountReserved := ctx.GlobalUint64(utils.BlockAmountReserved.Name) chaindb, err := accessDb(ctx, stack) if err != nil { return err @@ -282,19 +283,39 @@ func pruneBlock(ctx *cli.Context) error { return errors.New("prune failed, did not specify the AncientPath") } - lock, _, err := fileutil.Flock(filepath.Join(oldAncientPath, "RPUNEFLOCK")) + blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, blockAmountReserved) if err != nil { - log.Error("file lock existed, please wait for the lock release", "err", err) return err } - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, BlockAmountReserved) + lock, exist, err := fileutil.Flock(filepath.Join(oldAncientPath, "PRUNEFLOCK")) if err != nil { + log.Error("file lock error", "err", err) return err } + if exist { + log.Debug("file lock existed, waiting for prune recovery and continue", "err", err) + if err := blockpruner.RecoverInterruption("chaindata", config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { + log.Error("Pruning failed", "err", err) + return err + } + lock.Release() + log.Info("Block prune successfully") + return nil + } + if _, err := os.Stat(newAncientPath); err == nil { + // No file lock found for old ancientDB but new ancientDB exsisted, indicating the geth was interrupted + // after old ancientDB removal, this happened after backup successfully, so just rename the new ancientDB + if err := blockpruner.AncientDbReplacer(); err != nil { + log.Error("Failed to rename new ancient directory") + return err + } + log.Info("Block prune successfully") + return nil + } name := "chaindata" - if err := blockpruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { + if err := blockpruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false, false); err != nil { log.Error("Failed to back up block", "err", err) return err } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index f52fb997ca..6489a600fb 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -294,20 +294,6 @@ func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValu return nil // Can't find the data anywhere. } -func ReadOffSetOfAncientFreezer(db ethdb.KeyValueStore) uint64 { - offset, _ := db.Get(offSetOfAncientFreezer) - if offset == nil { - return 0 - } - return new(big.Int).SetBytes(offset).Uint64() -} - -func WriteOffSetOfAncientFreezer(db ethdb.KeyValueStore, offset uint64) { - if err := db.Put(offSetOfAncientFreezer, new(big.Int).SetUint64(offset).Bytes()); err != nil { - log.Crit("Failed to store offSetOfAncientFreezer", "err", err) - } -} - // HasHeader verifies the existence of a block header corresponding to the hash. func HasHeader(db ethdb.Reader, hash common.Hash, number uint64) bool { if has, err := db.Ancient(freezerHashTable, number); err == nil && common.BytesToHash(has) == hash { diff --git a/core/rawdb/database.go b/core/rawdb/database.go index e42b7ee5a9..b976f39db4 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -20,7 +20,10 @@ import ( "bytes" "errors" "fmt" + "math/big" "os" + "path/filepath" + "sync" "sync/atomic" "time" @@ -148,13 +151,58 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { } } +func ReadOffSetOfCurrentAncientFreezer(db ethdb.KeyValueReader) uint64 { + offset, _ := db.Get(offSetOfCurrentAncientFreezer) + if offset == nil { + return 0 + } + return new(big.Int).SetBytes(offset).Uint64() +} + +func ReadOffSetOfLastAncientFreezer(db ethdb.KeyValueReader) uint64 { + offset, _ := db.Get(offSetOfLastAncientFreezer) + if offset == nil { + return 0 + } + return new(big.Int).SetBytes(offset).Uint64() +} + +func WriteOffSetOfCurrentAncientFreezer(db ethdb.KeyValueWriter, offset uint64) { + if err := db.Put(offSetOfCurrentAncientFreezer, new(big.Int).SetUint64(offset).Bytes()); err != nil { + log.Crit("Failed to store offSetOfAncientFreezer", "err", err) + } +} +func WriteOffSetOfLastAncientFreezer(db ethdb.KeyValueWriter, offset uint64) { + if err := db.Put(offSetOfLastAncientFreezer, new(big.Int).SetUint64(offset).Bytes()); err != nil { + log.Crit("Failed to store offSetOfAncientFreezer", "err", err) + } +} + //NewFreezerDb only create a freezer without statedb. -func NewFreezerDb(freezer, namespace string, readonly bool) (*freezer, error) { - // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace, readonly) - if err != nil { - return nil, err +func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, lastOffSet, newOffSet uint64) (*freezer, error) { + wg := sync.WaitGroup{} + wg.Add(1) + // Create the idle freezer instance, this operation should be atomic to avoid mismatch between offset and acientDB. + var frdb *freezer + var errors error + go func() { + frdb, errors = newFreezer(frz, namespace, readonly) + if errors != nil { + return + } + frdb.offset = newOffSet + offsetBatch := db.NewBatch() + WriteOffSetOfCurrentAncientFreezer(offsetBatch, newOffSet) + WriteOffSetOfLastAncientFreezer(offsetBatch, lastOffSet) + if err := offsetBatch.Write(); err != nil { + log.Crit("Failed to write offset into disk", "err", err) + } + wg.Done() + }() + if errors != nil { + return nil, errors } + wg.Wait() return frdb, nil } @@ -167,7 +215,22 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st if err != nil { return nil, err } - offset := ReadOffSetOfAncientFreezer(db) + + var offset uint64 + path, name := filepath.Split(freezer) + if _, err := os.Stat(filepath.Join(path, "ancient_back")); os.IsNotExist(err) { + offset = ReadOffSetOfCurrentAncientFreezer(db) + } else { + // Indicating the offline block prune process was interrupted in backup step, + // if in current context opening old ancinetDB, should use offset of last version, because new offset already record + // into kvdb + if name == "ancient" { + offset = ReadOffSetOfLastAncientFreezer(db) + } else { + offset = ReadOffSetOfCurrentAncientFreezer(db) + } + + } frdb.offset = offset @@ -317,7 +380,7 @@ func (s *stat) Count() string { return s.count.String() } func AncientInspect(db ethdb.Database) error { - offset := counter(ReadOffSetOfAncientFreezer(db)) + offset := counter(ReadOffSetOfCurrentAncientFreezer(db)) // Get number of ancient rows inside the freezer ancients := counter(0) if count, err := db.Ancients(); err == nil { diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 209fddcb41..3d2179f2a0 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -142,11 +142,6 @@ func newFreezer(datadir string, namespace string, readonly bool) (*freezer, erro return freezer, nil } -func (f *freezer) SetOffSet(offset uint64) error { - f.offset = offset - return nil -} - // Close terminates the chain freezer, unmapping all the data files. func (f *freezer) Close() error { var errs []error diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 4187966b88..ece5006c39 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -69,7 +69,12 @@ var ( // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") - offSetOfAncientFreezer = []byte("OffSetOfAncientFreezer") + //offSet of new updated ancientDB. + offSetOfCurrentAncientFreezer = []byte("offSetOfCurrentAncientFreezer") + + //offSet of the ancientDB before updated version. + offSetOfLastAncientFreezer = []byte("offSetOfLastAncientFreezer") + // badBlockKey tracks the list of bad blocks seen by local badBlockKey = []byte("InvalidBlock") diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 8a2b3b0414..137d0a0174 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -254,7 +254,9 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta return nil } -func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace string, readonly bool) error { +func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace string, readonly, interrupt bool) error { + + //Open old db wrapper. chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.oldAncientPath, namespace, readonly, true) if err != nil { log.Error("Failed to open ancient database", "err=", err) @@ -262,15 +264,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str } defer chainDb.Close() - //Create new ancientdb backup. - frdbBack, err := rawdb.NewFreezerDb(p.newAncientPath, namespace, readonly) - if err != nil { - log.Error("Failed to create ancient freezer backup", "err=", err) - return err - } - defer frdbBack.Close() - - // Get the number of items in ancient db, frozen it is. + // Get the number of items in old ancient db, i.e. frozen. frozen, err := chainDb.Ancients() // If we can't access the freezer or it's empty, abort. @@ -278,25 +272,48 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str log.Error("can't access the freezer or it's empty, abort") return errors.New("can't access the freezer or it's empty, abort") } + //If the items in freezer is less than the block amount that we want to reserve, it is not enough, should stop. if frozen < p.BlockAmountReserved { log.Error("the number of old blocks is not enough to reserve,", "frozen items", frozen, "the amount specified", p.BlockAmountReserved) return errors.New("the number of old blocks is not enough to reserve") } - oldOffSet := rawdb.ReadOffSetOfAncientFreezer(chainDb) + var oldOffSet uint64 + if interrupt { + //The interrupt scecario within this function is specific for old and new ancientDB exsisted concurrently, + //should use last version of offset for oldAncientDB, because current offset is + //acutally of the new ancientDB_Backup, but what we want is the offset of ancientDB being backup. + oldOffSet = rawdb.ReadOffSetOfLastAncientFreezer(chainDb) + } else { + //Using current version of ancientDB for oldOffSet because the db for backup is current version. + oldOffSet = rawdb.ReadOffSetOfCurrentAncientFreezer(chainDb) + } + // Get the start BlockNumber for pruning. startBlockNumber := oldOffSet + frozen - p.BlockAmountReserved + + // Create new ancientdb backup and record the new and last version of offset in kvDB as well. // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. - frdbBack.SetOffSet(startBlockNumber) - // Write the new offset into statedb for the future new freezer usage. - rawdb.WriteOffSetOfAncientFreezer(chainDb, startBlockNumber) + frdbBack, err := rawdb.NewFreezerDb(chainDb, p.newAncientPath, namespace, readonly, oldOffSet, startBlockNumber) + if err != nil { + log.Error("Failed to create ancient freezer backup", "err=", err) + return err + } + defer frdbBack.Close() log.Info("prune info", "old offset", oldOffSet, "frozen items number", frozen, "amount to reserver", p.BlockAmountReserved) log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) - // All ancient data after and including startBlockNumber should write into new ancient_back. - for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { + // Get the number of items in ancient db, frozen it is. + frdbBackFrozen, err := frdbBack.Ancients() + if err != nil { + log.Error("can't access the new freezer, abort") + return errors.New("can't access the new freezer, abort") + } + + // All ancient data after and including startBlockNumber should write into new ancientDB ancient_back. + for blockNumber := frdbBackFrozen + startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) @@ -309,33 +326,69 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str //Write into new ancient_back db. rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) - log.Info("backup blockNumber ", "blockNumber", blockNumber) + log.Info("backup blockNumber successfully", "blockNumber", blockNumber) } return nil } // Backup the ancient data for the old ancient db, i.e. the most recent 128 blocks in ancient db. -func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespace string, readonly bool) error { +func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespace string, readonly, interrupt bool) error { start := time.Now() - if err := p.backUpOldDb(name, cache, handles, namespace, readonly); err != nil { + if err := p.backUpOldDb(name, cache, handles, namespace, readonly, interrupt); err != nil { return err } - // Open the new freezer backup directory backFreezer for assemble with statedb together, - //in the db wrapper, using the same kv db but only change the ancient db, /chaindb/ancient_backup. - chainDbBack, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.newAncientPath, namespace, readonly, true) + log.Info("Block pruning BackUp successfully", "time duration since start is", common.PrettyDuration(time.Since(start))) + return nil +} + +func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, namespace string, readonly bool) error { + + newExist, err := CheckAncientDbExist(p.newAncientPath) if err != nil { + log.Error("newAncientDb path error") return err } - defer chainDbBack.Close() - log.Info("Block pruning BackUp successfully", "time duration since start is", common.PrettyDuration(time.Since(start))) + if newExist { + //Indicating old and new ancientDB existed concurrently. + if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, true); err != nil { + log.Error("Failed to prune") + return err + } + if err := p.AncientDbReplacer(); err != nil { + log.Error("Failed to replace ancientDB") + return err + } + } else { + //Indicating new ancientDB even did not be created, just prune starting from backup from startBlockNumber. + if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, false); err != nil { + log.Error("Failed to prune") + return err + } + if err := p.AncientDbReplacer(); err != nil { + log.Error("Failed to replace ancientDB") + return err + } + } + return nil } +func CheckAncientDbExist(path string) (bool, error) { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + //Indicating the file didn't exist. + return false, nil + } + return true, err + } + return true, nil +} + func (p *BlockPruner) AncientDbReplacer() error { // Delete directly for the old ancientdb, e.g.: path ../chaindb/ancient if err := os.RemoveAll(p.oldAncientPath); err != nil { From 6b4031a1988cd09577dff2390d4f5143462fa5ca Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 4 Jan 2022 15:05:07 +0800 Subject: [PATCH 16/26] refined goimports --- cmd/geth/snapshot.go | 5 +++-- cmd/utils/flags.go | 4 ++-- core/rawdb/database.go | 8 ++++---- node/node.go | 4 ++-- rlp/safe.go | 2 +- tests/fuzzers/bls12381/bls12381_fuzz.go | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 340d803c9a..15cd1789c2 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -24,6 +24,9 @@ import ( "path/filepath" "time" + "github.com/prometheus/tsdb/fileutil" + cli "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -36,8 +39,6 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" - "github.com/prometheus/tsdb/fileutil" - cli "gopkg.in/urfave/cli.v1" ) var ( diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ef710056c6..9a5e1ef3fe 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1888,7 +1888,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disablFreeze bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1901,7 +1901,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disablFreez chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disablFreeze) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disableFreeze) } if err != nil { Fatalf("Could not open database: %v", err) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b976f39db4..854acd2fe4 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -209,7 +209,7 @@ func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, // 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, disablFreeze bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, disableFreeze bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newFreezer(freezer, namespace, readonly) if err != nil { @@ -303,7 +303,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } // Freezer is consistent with the key-value database, permit combining the two - if !disablFreeze && !frdb.readonly { + if !disableFreeze && !frdb.readonly { go frdb.freeze(db) } return &freezerdb{ @@ -337,12 +337,12 @@ 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, disablFreeze bool) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly, disableFreeze 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, disablFreeze) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, disableFreeze) if err != nil { kvdb.Close() return nil, err diff --git a/node/node.go b/node/node.go index 2849cc6ac7..778e448259 100644 --- a/node/node.go +++ b/node/node.go @@ -606,7 +606,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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, disablFreeze bool) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly, disableFreeze bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -625,7 +625,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, disablFreeze) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, disableFreeze) } if err == nil { diff --git a/rlp/safe.go b/rlp/safe.go index c881650a0d..a80380aef4 100644 --- a/rlp/safe.go +++ b/rlp/safe.go @@ -22,5 +22,5 @@ import "reflect" // byteArrayBytes returns a slice of the byte array v. func byteArrayBytes(v reflect.Value) []byte { - return v.Slice(0, v.Len()).Bytes() + return v.Slice(0, v.Len()).Bytes() } diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go index 298050ad36..5c28b952d3 100644 --- a/tests/fuzzers/bls12381/bls12381_fuzz.go +++ b/tests/fuzzers/bls12381/bls12381_fuzz.go @@ -28,6 +28,7 @@ import ( gnark "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/ethereum/go-ethereum/crypto/bls12381" ) @@ -159,7 +160,7 @@ func FuzzCrossG1MultiExp(data []byte) int { gethPoints = append(gethPoints, new(bls12381.PointG1).Set(kp1)) gnarkPoints = append(gnarkPoints, *cp1) } - if len(gethScalars) == 0{ + if len(gethScalars) == 0 { return 0 } // compute multi exponentiation From 8e051d47d1f520979d36deea5450beb00ef2de81 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 6 Jan 2022 14:28:20 +0800 Subject: [PATCH 17/26] addressed comments from walt --- cmd/geth/snapshot.go | 30 +++++++++++++++--------------- cmd/utils/flags.go | 7 ++++--- core/rawdb/database.go | 36 +++++++++++++++--------------------- core/state/pruner/pruner.go | 3 ++- node/node.go | 3 ++- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 15cd1789c2..9ca12ab999 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -47,9 +47,6 @@ var ( // emptyCode is the known hash of the empty EVM bytecode. emptyCode = crypto.Keccak256(nil) - - //the layer of tries trees that keep in memory - TriesInMemory = 128 ) var ( @@ -99,6 +96,7 @@ the trie clean cache with default directory will be deleted. utils.DataDirFlag, utils.AncientFlag, utils.BlockAmountReserved, + utils.TriesInMemoryFlag, }, Description: ` geth offline prune-block for block data in ancientdb. @@ -182,6 +180,8 @@ It's also usable without snapshot enabled. ) func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { + //The layer of tries trees that keep in memory. + TriesInMemory := int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) chaindb := utils.MakeChainDatabase(ctx, stack, false, true) defer chaindb.Close() @@ -193,6 +193,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { //Make sure the MPT and snapshot matches before pruning, otherwise the node can not start. snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, TriesInMemory, headBlock.Root(), false, false, false) if err != nil { + log.Error("snaptree error", "err", err) return nil, err // The relevant snapshot(s) might not exist } @@ -208,6 +209,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { // Reject if the accumulated diff layers are less than n. It // means in most of normal cases, there is no associated state // with bottom-most diff layer. + log.Error("snapshot layers != TriesInMemory", "err", err) return nil, fmt.Errorf("snapshot not old enough yet: need %d more blocks", TriesInMemory-len(layers)) } // Use the bottom-most diff layer as the target @@ -246,6 +248,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { } if !found { if len(layers) > 0 { + log.Error("no snapshot paired state") return nil, errors.New("no snapshot paired state") } return nil, fmt.Errorf("associated state[%x] is not present", targetRoot) @@ -257,10 +260,6 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { log.Info("Selecting user-specified state as the pruning target", "root", targetRoot) } } - if err := snaptree.Verify(targetRoot); err != nil { - log.Error("Failed to verfiy state", "root", targetRoot, "err", err) - return nil, err - } return chaindb, nil } @@ -272,13 +271,14 @@ func pruneBlock(ctx *cli.Context) error { if err != nil { return err } - var oldAncientPath, newAncientPath string - path, _ := filepath.Split(ctx.GlobalString(utils.AncientFlag.Name)) + var newAncientPath string + oldAncientPath := ctx.GlobalString(utils.AncientFlag.Name) + if !filepath.IsAbs(oldAncientPath) { + oldAncientPath = stack.ResolvePath(oldAncientPath) + } + + path, _ := filepath.Split(oldAncientPath) if path != "" { - if !filepath.IsAbs(path) { - path = stack.ResolvePath(path) - } - oldAncientPath = filepath.Join(path, "ancient") newAncientPath = filepath.Join(path, "ancient_back") } else { return errors.New("prune failed, did not specify the AncientPath") @@ -295,7 +295,7 @@ func pruneBlock(ctx *cli.Context) error { return err } if exist { - log.Debug("file lock existed, waiting for prune recovery and continue", "err", err) + log.Warn("file lock existed, waiting for prune recovery and continue", "err", err) if err := blockpruner.RecoverInterruption("chaindata", config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { log.Error("Pruning failed", "err", err) return err @@ -321,7 +321,7 @@ func pruneBlock(ctx *cli.Context) error { return err } - log.Info("geth block offline pruning backup successfully") + log.Info("backup block successfully") //After backing up successfully, rename the new ancientdb name to the original one, and delete the old ancientdb if err := blockpruner.AncientDbReplacer(); err != nil { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9a5e1ef3fe..96c0d8b860 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -33,6 +33,10 @@ import ( "text/template" "time" + pcsclite "github.com/gballet/go-libpcsclite" + gopsutil "github.com/shirou/gopsutil/mem" + "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -66,9 +70,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - pcsclite "github.com/gballet/go-libpcsclite" - gopsutil "github.com/shirou/gopsutil/mem" - "gopkg.in/urfave/cli.v1" ) func init() { diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 854acd2fe4..2f6a52e750 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -27,12 +27,13 @@ import ( "sync/atomic" "time" + "github.com/olekukonko/tablewriter" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/log" - "github.com/olekukonko/tablewriter" ) // freezerdb is a database wrapper that enabled freezer data retrievals. @@ -183,25 +184,18 @@ func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, wg := sync.WaitGroup{} wg.Add(1) // Create the idle freezer instance, this operation should be atomic to avoid mismatch between offset and acientDB. - var frdb *freezer - var errors error - go func() { - frdb, errors = newFreezer(frz, namespace, readonly) - if errors != nil { - return - } - frdb.offset = newOffSet - offsetBatch := db.NewBatch() - WriteOffSetOfCurrentAncientFreezer(offsetBatch, newOffSet) - WriteOffSetOfLastAncientFreezer(offsetBatch, lastOffSet) - if err := offsetBatch.Write(); err != nil { - log.Crit("Failed to write offset into disk", "err", err) - } - wg.Done() - }() + frdb, errors := newFreezer(frz, namespace, readonly) if errors != nil { return nil, errors } + frdb.offset = newOffSet + offsetBatch := db.NewBatch() + WriteOffSetOfCurrentAncientFreezer(offsetBatch, newOffSet) + WriteOffSetOfLastAncientFreezer(offsetBatch, lastOffSet) + if err := offsetBatch.Write(); err != nil { + log.Crit("Failed to write offset into disk", "err", err) + } + wg.Done() wg.Wait() return frdb, nil } @@ -221,13 +215,13 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st if _, err := os.Stat(filepath.Join(path, "ancient_back")); os.IsNotExist(err) { offset = ReadOffSetOfCurrentAncientFreezer(db) } else { - // Indicating the offline block prune process was interrupted in backup step, + // "ancient_back" exists, indicating the offline block prune process was interrupted in backup step, // if in current context opening old ancinetDB, should use offset of last version, because new offset already record // into kvdb - if name == "ancient" { - offset = ReadOffSetOfLastAncientFreezer(db) - } else { + if name == "ancient_back" { offset = ReadOffSetOfCurrentAncientFreezer(db) + } else { + offset = ReadOffSetOfLastAncientFreezer(db) } } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 137d0a0174..0c233537fd 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -112,6 +112,7 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie bloomSize = 256 } stateBloom, err := newStateBloomWithSize(bloomSize) + if err != nil { return nil, err } @@ -326,7 +327,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str //Write into new ancient_back db. rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) - log.Info("backup blockNumber successfully", "blockNumber", blockNumber) + log.Info("backup block successfully", "blockNumber", blockNumber) } return nil diff --git a/node/node.go b/node/node.go index 778e448259..3af8c4a3e7 100644 --- a/node/node.go +++ b/node/node.go @@ -27,6 +27,8 @@ import ( "strings" "sync" + "github.com/prometheus/tsdb/fileutil" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" @@ -35,7 +37,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" - "github.com/prometheus/tsdb/fileutil" ) // Node is a container on which services can be registered. From a5160650dd0c461829d216765a97ecb7964493ac Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 6 Jan 2022 22:12:12 +0800 Subject: [PATCH 18/26] change the logic as restarting prune after interruption --- cmd/geth/chaincmd.go | 6 +++--- cmd/geth/dbcmd.go | 16 ++++++++-------- cmd/geth/pruneblock_test.go | 4 ++-- cmd/geth/snapshot.go | 12 ++++++------ cmd/utils/flags.go | 8 ++++---- core/blockchain_repair_test.go | 4 ++-- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 4 ++-- core/blockchain_test.go | 18 +++++++++--------- core/rawdb/accessors_chain_test.go | 2 +- core/rawdb/database.go | 23 +++++++---------------- core/state/pruner/pruner.go | 29 ++++++++++++++++------------- node/node.go | 6 +++--- 13 files changed, 64 insertions(+), 70 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index a30efbbdea..9bcbb88762 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -458,7 +458,7 @@ func importPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false, false) start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -477,7 +477,7 @@ func exportPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -491,7 +491,7 @@ func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) for _, arg := range ctx.Args() { var header *types.Header if hashish(arg) { diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 8c7ac6226e..fc285e9a07 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -293,7 +293,7 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) defer db.Close() return rawdb.InspectDatabase(db, prefix, start) @@ -303,7 +303,7 @@ func ancientInspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, true) + db := utils.MakeChainDatabase(ctx, stack, true, true, false) defer db.Close() return rawdb.AncientInspect(db) } @@ -325,7 +325,7 @@ func dbStats(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) defer db.Close() showLeveldbStats(db) @@ -336,7 +336,7 @@ func dbCompact(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false, false) defer db.Close() log.Info("Stats before compaction") @@ -360,7 +360,7 @@ func dbGet(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -385,7 +385,7 @@ func dbDelete(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -412,7 +412,7 @@ func dbPut(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false, false) defer db.Close() var ( @@ -446,7 +446,7 @@ func dbDumpTrie(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, false, false) defer db.Close() var ( root []byte diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index c64d13d925..3349828a35 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -94,7 +94,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 t.Fatalf("Failed to back up block: %v", err) } - dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false, true) + dbBack, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, newAncientPath, "", false, true, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } @@ -137,7 +137,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemain uint64) (ethdb.Database, []*types.Block, []*types.Block, []types.Receipts, []*big.Int, uint64, *core.BlockChain) { //create a database with ancient freezer - db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(chaindbPath, 0, 0, AncientPath, "", false, false, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 9ca12ab999..4346e8a98f 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -182,7 +182,7 @@ It's also usable without snapshot enabled. func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { //The layer of tries trees that keep in memory. TriesInMemory := int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) - chaindb := utils.MakeChainDatabase(ctx, stack, false, true) + chaindb := utils.MakeChainDatabase(ctx, stack, false, true, false) defer chaindb.Close() headBlock := rawdb.ReadHeadBlock(chaindb) @@ -295,7 +295,7 @@ func pruneBlock(ctx *cli.Context) error { return err } if exist { - log.Warn("file lock existed, waiting for prune recovery and continue", "err", err) + log.Info("file lock existed, waiting for prune recovery and continue", "err", err) if err := blockpruner.RecoverInterruption("chaindata", config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { log.Error("Pruning failed", "err", err) return err @@ -337,7 +337,7 @@ func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, false, false, false) pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name), ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) if err != nil { log.Error("Failed to open snapshot tree", "err", err) @@ -366,7 +366,7 @@ func verifyState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -404,7 +404,7 @@ func traverseState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -494,7 +494,7 @@ func traverseRawState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 96c0d8b860..fd643c4d77 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1770,7 +1770,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DataDirFlag.Name) { // Check if we have an already initialized chain and fall back to // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack, false, false) // TODO (MariusVanDerWijden) make this read only + chaindb := MakeChainDatabase(ctx, stack, false, false, false) // TODO (MariusVanDerWijden) make this read only if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } @@ -1889,7 +1889,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze, isLastOffset bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1902,7 +1902,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFree chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disableFreeze) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disableFreeze, isLastOffset) } if err != nil { Fatalf("Could not open database: %v", err) @@ -1932,7 +1932,7 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { // MakeChain creates a chain manager from set command line flags. func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) { var err error - chainDb = MakeChainDatabase(ctx, stack, false, false) // TODO(rjl493456442) support read-only database + chainDb = MakeChainDatabase(ctx, stack, false, false, false) // TODO(rjl493456442) support read-only database config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) if err != nil { Fatalf("%v", err) diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index cfdf9d6572..b85f9c9e00 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1762,7 +1762,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -1832,7 +1832,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { db.Close() // Start a new blockchain back up and see where the repait leads us - db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false, false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index df3481fc78..bdcca988a3 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1961,7 +1961,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index da7c3f444d..530ad035dd 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -64,7 +64,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false, false, false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -248,7 +248,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { db.Close() // Start a new blockchain back up and see where the repair leads us - newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false, false) + newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false, false, false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 45a77844f9..8078db774f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -653,7 +653,7 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -727,7 +727,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false) + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1594,7 +1594,7 @@ func TestBlockchainRecovery(t *testing.T) { } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1651,7 +1651,7 @@ func TestIncompleteAncientReceiptChainInsertion(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1850,7 +1850,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false) + chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2130,7 +2130,7 @@ func TestTransactionIndices(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2158,7 +2158,7 @@ func TestTransactionIndices(t *testing.T) { // Init block chain with external ancients, check all needed indices has been indexed. limit := []uint64{0, 32, 64, 128} for _, l := range limit { - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2178,7 +2178,7 @@ func TestTransactionIndices(t *testing.T) { } // Reconstruct a block chain which only reserves HEAD-64 tx indices - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2257,7 +2257,7 @@ func TestSkipStaleTxIndicesInFastSync(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false) + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index b2810abd72..f99511e456 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -440,7 +440,7 @@ func TestAncientStorage(t *testing.T) { } defer os.Remove(frdir) - db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false) + db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false, false, false) if err != nil { t.Fatalf("failed to create database with ancient backend") } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 2f6a52e750..577dbedff7 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -22,7 +22,6 @@ import ( "fmt" "math/big" "os" - "path/filepath" "sync" "sync/atomic" "time" @@ -203,7 +202,7 @@ func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, // 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, disableFreeze bool) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly, disableFreeze, isLastOffset bool) (ethdb.Database, error) { // Create the idle freezer instance frdb, err := newFreezer(freezer, namespace, readonly) if err != nil { @@ -211,19 +210,11 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } var offset uint64 - path, name := filepath.Split(freezer) - if _, err := os.Stat(filepath.Join(path, "ancient_back")); os.IsNotExist(err) { - offset = ReadOffSetOfCurrentAncientFreezer(db) + //The offset of ancientDB should be handled differently in different scenarios + if isLastOffset { + offset = ReadOffSetOfLastAncientFreezer(db) } else { - // "ancient_back" exists, indicating the offline block prune process was interrupted in backup step, - // if in current context opening old ancinetDB, should use offset of last version, because new offset already record - // into kvdb - if name == "ancient_back" { - offset = ReadOffSetOfCurrentAncientFreezer(db) - } else { - offset = ReadOffSetOfLastAncientFreezer(db) - } - + offset = ReadOffSetOfCurrentAncientFreezer(db) } frdb.offset = offset @@ -331,12 +322,12 @@ 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, disableFreeze bool) (ethdb.Database, error) { +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly, disableFreeze, isLastOffset 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, disableFreeze) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly, disableFreeze, isLastOffset) if err != nil { kvdb.Close() return nil, err diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 0c233537fd..feb29ac2e7 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -258,7 +258,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace string, readonly, interrupt bool) error { //Open old db wrapper. - chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.oldAncientPath, namespace, readonly, true) + chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.oldAncientPath, namespace, readonly, true, interrupt) if err != nil { log.Error("Failed to open ancient database", "err=", err) return err @@ -306,15 +306,9 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str log.Info("prune info", "old offset", oldOffSet, "frozen items number", frozen, "amount to reserver", p.BlockAmountReserved) log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) - // Get the number of items in ancient db, frozen it is. - frdbBackFrozen, err := frdbBack.Ancients() - if err != nil { - log.Error("can't access the new freezer, abort") - return errors.New("can't access the new freezer, abort") - } - + start := time.Now() // All ancient data after and including startBlockNumber should write into new ancientDB ancient_back. - for blockNumber := frdbBackFrozen + startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { + for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) @@ -327,9 +321,12 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str //Write into new ancient_back db. rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) - log.Info("backup block successfully", "blockNumber", blockNumber) + if common.PrettyDuration(time.Since(start)) > common.PrettyDuration(5*time.Second) { + log.Info("block backup process running successfully", "current blockNumber for backup", blockNumber) + start = time.Now() + } } - + log.Info("block back up done", "current start blockNumber in ancientDB", startBlockNumber) return nil } @@ -355,7 +352,12 @@ func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, names } if newExist { - //Indicating old and new ancientDB existed concurrently. + // Indicating old and new ancientDB existed concurrently. + // Delete directly for the new ancientdb to prune from start, e.g.: path ../chaindb/ancient_backup + if err := os.RemoveAll(p.newAncientPath); err != nil { + log.Error("Failed to remove old ancient directory %v", err) + return err + } if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, true); err != nil { log.Error("Failed to prune") return err @@ -365,7 +367,8 @@ func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, names return err } } else { - //Indicating new ancientDB even did not be created, just prune starting from backup from startBlockNumber. + // Indicating new ancientDB even did not be created, just prune starting at backup from startBlockNumber as usual, + // in this case, the new offset have not been written into kvDB. if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, false); err != nil { log.Error("Failed to prune") return err diff --git a/node/node.go b/node/node.go index 3af8c4a3e7..b6e1246b3d 100644 --- a/node/node.go +++ b/node/node.go @@ -587,7 +587,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di if persistDiff { chainDataHandles = handles * chainDataHandlesPercentage / 100 } - chainDB, err := n.OpenDatabaseWithFreezer(name, cache, chainDataHandles, freezer, namespace, readonly, false) + chainDB, err := n.OpenDatabaseWithFreezer(name, cache, chainDataHandles, freezer, namespace, readonly, false, false) if err != nil { return nil, err } @@ -607,7 +607,7 @@ func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, di // 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, disableFreeze bool) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly, disableFreeze, isLastOffset bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -626,7 +626,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, disableFreeze) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly, disableFreeze, isLastOffset) } if err == nil { From de8641795e1c05738951663b66aa02aa049998d5 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 11 Jan 2022 09:34:15 +0800 Subject: [PATCH 19/26] addressed comments --- cmd/geth/dbcmd.go | 2 +- core/rawdb/database.go | 15 ++------------- core/state/pruner/pruner.go | 12 ++++++++++-- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index fc285e9a07..483e3ca98a 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -204,7 +204,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, }, Usage: "Inspect the ancientStore information", Description: `This commands will read current offset from kvdb, which is the current offset and starting BlockNumber - of ancientStore, will also displays the reserved number of blocks in ancientStore `, +of ancientStore, will also displays the reserved number of blocks in ancientStore `, } ) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 577dbedff7..933b7c05a8 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -22,7 +22,6 @@ import ( "fmt" "math/big" "os" - "sync" "sync/atomic" "time" @@ -178,24 +177,14 @@ func WriteOffSetOfLastAncientFreezer(db ethdb.KeyValueWriter, offset uint64) { } } -//NewFreezerDb only create a freezer without statedb. -func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, lastOffSet, newOffSet uint64) (*freezer, error) { - wg := sync.WaitGroup{} - wg.Add(1) +// NewFreezerDb only create a freezer without statedb. +func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, newOffSet uint64) (*freezer, error) { // Create the idle freezer instance, this operation should be atomic to avoid mismatch between offset and acientDB. frdb, errors := newFreezer(frz, namespace, readonly) if errors != nil { return nil, errors } frdb.offset = newOffSet - offsetBatch := db.NewBatch() - WriteOffSetOfCurrentAncientFreezer(offsetBatch, newOffSet) - WriteOffSetOfLastAncientFreezer(offsetBatch, lastOffSet) - if err := offsetBatch.Write(); err != nil { - log.Crit("Failed to write offset into disk", "err", err) - } - wg.Done() - wg.Wait() return frdb, nil } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index feb29ac2e7..5c4b4b4a6a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -296,13 +296,20 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str // Create new ancientdb backup and record the new and last version of offset in kvDB as well. // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. - frdbBack, err := rawdb.NewFreezerDb(chainDb, p.newAncientPath, namespace, readonly, oldOffSet, startBlockNumber) + frdbBack, err := rawdb.NewFreezerDb(chainDb, p.newAncientPath, namespace, readonly, startBlockNumber) if err != nil { log.Error("Failed to create ancient freezer backup", "err=", err) return err } defer frdbBack.Close() + offsetBatch := chainDb.NewBatch() + rawdb.WriteOffSetOfCurrentAncientFreezer(offsetBatch, startBlockNumber) + rawdb.WriteOffSetOfLastAncientFreezer(offsetBatch, oldOffSet) + if err := offsetBatch.Write(); err != nil { + log.Crit("Failed to write offset into disk", "err", err) + } + log.Info("prune info", "old offset", oldOffSet, "frozen items number", frozen, "amount to reserver", p.BlockAmountReserved) log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) @@ -319,8 +326,9 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str } externTd := new(big.Int).Add(block.Difficulty(), td) - //Write into new ancient_back db. + // Write into new ancient_back db. rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) + // Print the log every 5s for better trace. if common.PrettyDuration(time.Since(start)) > common.PrettyDuration(5*time.Second) { log.Info("block backup process running successfully", "current blockNumber for backup", blockNumber) start = time.Now() From f3e31a18f477aec137654cc8a4edf644b4118b5b Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 11 Jan 2022 18:42:06 +0800 Subject: [PATCH 20/26] reclaimed freezer logic --- core/rawdb/database.go | 6 ++++-- core/rawdb/freezer.go | 34 +++++++++++++++++----------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 933b7c05a8..d72969df75 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -360,9 +360,11 @@ func AncientInspect(db ethdb.Database) error { if count, err := db.Ancients(); err == nil { ancients = counter(count) } + endNumber := offset + ancients - 1 stats := [][]string{ - {"Offset/StartBlockNumber", "Offset/StartBlockNumber after BlockPrune", offset.String()}, - {"Amount of remained items in AncientStore", "remaining items after BlockPrune", ancients.String()}, + {"Offset/StartBlockNumber", "Offset/StartBlockNumber of ancientDB", offset.String()}, + {"Amount of remained items in AncientStore", "Remaining items of ancientDB", ancients.String()}, + {"The last BlockNumber within ancientDB", "The last BlockNumber", endNumber.String()}, } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Items"}) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 3d2179f2a0..a1a5407849 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -219,23 +219,23 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td }() // Inject all the components into the relevant data tables if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { - log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) + log.Error("Failed to append ancient hash", "number", f.frozen+f.offset, "hash", hash, "err", err) return err } if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { - log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) + log.Error("Failed to append ancient header", "number", f.frozen+f.offset, "hash", hash, "err", err) return err } if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil { - log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err) + log.Error("Failed to append ancient body", "number", f.frozen+f.offset, "hash", hash, "err", err) return err } if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { - log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) + log.Error("Failed to append ancient receipts", "number", f.frozen+f.offset, "hash", hash, "err", err) return err } if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { - log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) + log.Error("Failed to append ancient difficulty", "number", f.frozen+f.offset, "hash", hash, "err", err) return err } atomic.AddUint64(&f.frozen, 1) // Only modify atomically @@ -247,15 +247,15 @@ func (f *freezer) TruncateAncients(items uint64) error { if f.readonly { return errReadOnly } - if atomic.LoadUint64(&f.frozen) <= items { + if atomic.LoadUint64(&f.frozen) <= items-f.offset { return nil } for _, table := range f.tables { - if err := table.truncate(items); err != nil { + if err := table.truncate(items - f.offset); err != nil { return err } } - atomic.StoreUint64(&f.frozen, items) + atomic.StoreUint64(&f.frozen, items-f.offset) return nil } @@ -329,8 +329,8 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { backoff = true continue - case *number-threshold <= f.frozen: - log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) + case *number-threshold <= f.frozen+f.offset: + log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen+f.offset) backoff = true continue } @@ -354,30 +354,30 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { // Retrieves all the components of the canonical block hash := ReadCanonicalHash(nfdb, f.frozen+f.offset) if hash == (common.Hash{}) { - log.Error("Canonical hash missing, can't freeze", "number", f.frozen) + log.Error("Canonical hash missing, can't freeze", "number", f.frozen+f.offset) break } header := ReadHeaderRLP(nfdb, hash, f.frozen+f.offset) if len(header) == 0 { - log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash) + log.Error("Block header missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) break } body := ReadBodyRLP(nfdb, hash, f.frozen+f.offset) if len(body) == 0 { - log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash) + log.Error("Block body missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) break } receipts := ReadReceiptsRLP(nfdb, hash, f.frozen+f.offset) if len(receipts) == 0 { - log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash) + log.Error("Block receipts missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) break } td := ReadTdRLP(nfdb, hash, f.frozen+f.offset) if len(td) == 0 { - log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash) + log.Error("Total difficulty missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) break } - log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash) + log.Trace("Deep froze ancient block", "number", f.frozen+f.offset, "hash", hash) // Inject all the components into the relevant data tables if err := f.AppendAncient(f.frozen+f.offset, hash[:], header, body, receipts, td); err != nil { break @@ -454,7 +454,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { } // Log something friendly for the user context := []interface{}{ - "blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1, + "blocks", f.frozen + f.offset - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen + f.offset - 1, } if n := len(ancients); n > 0 { context = append(context, []interface{}{"hash", ancients[n-1]}...) From 69cdcfe57a8efc873a2a9ce1fde35bb98b3a9d5e Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 13 Jan 2022 13:14:15 +0800 Subject: [PATCH 21/26] introduce flag to enable/disable check between MPT and snapshot --- cmd/geth/main.go | 1 + cmd/geth/snapshot.go | 4 ++++ cmd/geth/usage.go | 1 + cmd/utils/flags.go | 5 +++++ 4 files changed, 11 insertions(+) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0ad7dba734..3576fe2e46 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -166,6 +166,7 @@ var ( configFileFlag, utils.CatalystFlag, utils.BlockAmountReserved, + utils.CheckSnapshotWithMPT, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 4346e8a98f..d13f9a9150 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -97,6 +97,7 @@ the trie clean cache with default directory will be deleted. utils.AncientFlag, utils.BlockAmountReserved, utils.TriesInMemoryFlag, + utils.CheckSnapshotWithMPT, }, Description: ` geth offline prune-block for block data in ancientdb. @@ -185,6 +186,9 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { chaindb := utils.MakeChainDatabase(ctx, stack, false, true, false) defer chaindb.Close() + if !ctx.GlobalBool(utils.CheckSnapshotWithMPT.Name) { + return chaindb, nil + } headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { return nil, errors.New("failed to load head block") diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index f42d40ec01..a1a0a7d0b4 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -59,6 +59,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.WhitelistFlag, utils.TriesInMemoryFlag, utils.BlockAmountReserved, + utils.CheckSnapshotWithMPT, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fd643c4d77..2e2c432024 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -833,6 +833,11 @@ var ( Name: "block-amount-reserved", Usage: "Sets the expected remained amount of blocks for offline block prune", } + + CheckSnapshotWithMPT = cli.BoolFlag{ + Name: "check-snapshot-with-mpt", + Usage: "Enable checking between snapshot and MPT ", + } ) // MakeDataDir retrieves the currently requested data directory, terminating From f9ea6a28177afad7d7f93f300d36f9a59d72b768 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Thu, 13 Jan 2022 22:11:11 +0800 Subject: [PATCH 22/26] update the logic of frozen field in freezerDB --- core/rawdb/database.go | 7 +++- core/rawdb/freezer.go | 81 ++++++++++++++++++------------------- core/state/pruner/pruner.go | 18 ++++----- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index d72969df75..6e488befca 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -185,6 +185,7 @@ func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, return nil, errors } frdb.offset = newOffSet + frdb.frozen += newOffSet return frdb, nil } @@ -208,6 +209,10 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st frdb.offset = offset + // Some blocks in ancientDB may have already been frozen and been pruned, so adding the offset to + // reprensent the absolute number of blocks already frozen. + frdb.frozen += offset + // Since the freezer can be stored separately from the user's key-value database, // there's a fairly high probability that the user requests invalid combinations // of the freezer and database. Ensure that we don't shoot ourselves in the foot @@ -355,7 +360,7 @@ func (s *stat) Count() string { } func AncientInspect(db ethdb.Database) error { offset := counter(ReadOffSetOfCurrentAncientFreezer(db)) - // Get number of ancient rows inside the freezer + // Get number of ancient rows inside the freezer. ancients := counter(0) if count, err := db.Ancients(); err == nil { ancients = counter(count) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a1a5407849..a98120fed8 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -86,7 +86,7 @@ type freezer struct { quit chan struct{} closeOnce sync.Once - offset uint64 + offset uint64 // Starting BlockNumber in current freezer } // newFreezer creates a chain freezer that moves ancient chain data into @@ -181,7 +181,7 @@ func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { // Ancients returns the length of the frozen items. func (f *freezer) Ancients() (uint64, error) { - return atomic.LoadUint64(&f.frozen), nil + return atomic.LoadUint64(&f.frozen) - atomic.LoadUint64(&f.offset), nil } // AncientSize returns the ancient size of the specified category. @@ -203,7 +203,7 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td return errReadOnly } // Ensure the binary blobs we are appending is continuous with freezer. - if atomic.LoadUint64(&f.frozen) != number-f.offset { + if atomic.LoadUint64(&f.frozen) != number { return errOutOrderInsertion } // Rollback all inserted data if any insertion below failed to ensure @@ -218,24 +218,24 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td } }() // Inject all the components into the relevant data tables - if err := f.tables[freezerHashTable].Append(f.frozen, hash[:]); err != nil { - log.Error("Failed to append ancient hash", "number", f.frozen+f.offset, "hash", hash, "err", err) + if err := f.tables[freezerHashTable].Append(f.frozen-f.offset, hash[:]); err != nil { + log.Error("Failed to append ancient hash", "number", f.frozen, "hash", hash, "err", err) return err } - if err := f.tables[freezerHeaderTable].Append(f.frozen, header); err != nil { - log.Error("Failed to append ancient header", "number", f.frozen+f.offset, "hash", hash, "err", err) + if err := f.tables[freezerHeaderTable].Append(f.frozen-f.offset, header); err != nil { + log.Error("Failed to append ancient header", "number", f.frozen, "hash", hash, "err", err) return err } - if err := f.tables[freezerBodiesTable].Append(f.frozen, body); err != nil { - log.Error("Failed to append ancient body", "number", f.frozen+f.offset, "hash", hash, "err", err) + if err := f.tables[freezerBodiesTable].Append(f.frozen-f.offset, body); err != nil { + log.Error("Failed to append ancient body", "number", f.frozen, "hash", hash, "err", err) return err } - if err := f.tables[freezerReceiptTable].Append(f.frozen, receipts); err != nil { - log.Error("Failed to append ancient receipts", "number", f.frozen+f.offset, "hash", hash, "err", err) + if err := f.tables[freezerReceiptTable].Append(f.frozen-f.offset, receipts); err != nil { + log.Error("Failed to append ancient receipts", "number", f.frozen, "hash", hash, "err", err) return err } - if err := f.tables[freezerDifficultyTable].Append(f.frozen, td); err != nil { - log.Error("Failed to append ancient difficulty", "number", f.frozen+f.offset, "hash", hash, "err", err) + if err := f.tables[freezerDifficultyTable].Append(f.frozen-f.offset, td); err != nil { + log.Error("Failed to append ancient difficulty", "number", f.frozen, "hash", hash, "err", err) return err } atomic.AddUint64(&f.frozen, 1) // Only modify atomically @@ -247,15 +247,15 @@ func (f *freezer) TruncateAncients(items uint64) error { if f.readonly { return errReadOnly } - if atomic.LoadUint64(&f.frozen) <= items-f.offset { + if atomic.LoadUint64(&f.frozen) <= items { return nil } for _, table := range f.tables { - if err := table.truncate(items - f.offset); err != nil { + if err := table.truncate(items); err != nil { return err } } - atomic.StoreUint64(&f.frozen, items-f.offset) + atomic.StoreUint64(&f.frozen, items) return nil } @@ -315,7 +315,6 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { continue } number := ReadHeaderNumber(nfdb, hash) - threshold := atomic.LoadUint64(&f.threshold) switch { @@ -329,8 +328,8 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { backoff = true continue - case *number-threshold <= f.frozen+f.offset: - log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen+f.offset) + case *number-threshold <= f.frozen: + log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen) backoff = true continue } @@ -342,44 +341,44 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { } // Seems we have data ready to be frozen, process in usable batches limit := *number - threshold - if limit-f.frozen-f.offset > freezerBatchLimit { - limit = f.frozen + f.offset + freezerBatchLimit + if limit-f.frozen > freezerBatchLimit { + limit = f.frozen + freezerBatchLimit } var ( start = time.Now() - first = f.frozen + f.offset - ancients = make([]common.Hash, 0, limit-f.frozen-f.offset) + first = f.frozen + ancients = make([]common.Hash, 0, limit-f.frozen) ) - for f.frozen+f.offset <= limit { + for f.frozen <= limit { // Retrieves all the components of the canonical block - hash := ReadCanonicalHash(nfdb, f.frozen+f.offset) + hash := ReadCanonicalHash(nfdb, f.frozen) if hash == (common.Hash{}) { - log.Error("Canonical hash missing, can't freeze", "number", f.frozen+f.offset) + log.Error("Canonical hash missing, can't freeze", "number", f.frozen) break } - header := ReadHeaderRLP(nfdb, hash, f.frozen+f.offset) + header := ReadHeaderRLP(nfdb, hash, f.frozen) if len(header) == 0 { - log.Error("Block header missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) + log.Error("Block header missing, can't freeze", "number", f.frozen, "hash", hash) break } - body := ReadBodyRLP(nfdb, hash, f.frozen+f.offset) + body := ReadBodyRLP(nfdb, hash, f.frozen) if len(body) == 0 { - log.Error("Block body missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) + log.Error("Block body missing, can't freeze", "number", f.frozen, "hash", hash) break } - receipts := ReadReceiptsRLP(nfdb, hash, f.frozen+f.offset) + receipts := ReadReceiptsRLP(nfdb, hash, f.frozen) if len(receipts) == 0 { - log.Error("Block receipts missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) + log.Error("Block receipts missing, can't freeze", "number", f.frozen, "hash", hash) break } - td := ReadTdRLP(nfdb, hash, f.frozen+f.offset) + td := ReadTdRLP(nfdb, hash, f.frozen) if len(td) == 0 { - log.Error("Total difficulty missing, can't freeze", "number", f.frozen+f.offset, "hash", hash) + log.Error("Total difficulty missing, can't freeze", "number", f.frozen, "hash", hash) break } - log.Trace("Deep froze ancient block", "number", f.frozen+f.offset, "hash", hash) + log.Trace("Deep froze ancient block", "number", f.frozen, "hash", hash) // Inject all the components into the relevant data tables - if err := f.AppendAncient(f.frozen+f.offset, hash[:], header, body, receipts, td); err != nil { + if err := f.AppendAncient(f.frozen, hash[:], header, body, receipts, td); err != nil { break } ancients = append(ancients, hash) @@ -404,7 +403,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { // Wipe out side chains also and track dangling side chians var dangling []common.Hash - for number := first; number < f.frozen+f.offset; number++ { + for number := first; number < f.frozen; number++ { // Always keep the genesis block in active database if number != 0 { dangling = ReadAllHashes(db, number) @@ -420,8 +419,8 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { batch.Reset() // Step into the future and delete and dangling side chains - if f.frozen+f.offset > 0 { - tip := f.frozen + f.offset + if f.frozen > 0 { + tip := f.frozen for len(dangling) > 0 { drop := make(map[common.Hash]struct{}) for _, hash := range dangling { @@ -454,7 +453,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { } // Log something friendly for the user context := []interface{}{ - "blocks", f.frozen + f.offset - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen + f.offset - 1, + "blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1, } if n := len(ancients); n > 0 { context = append(context, []interface{}{"hash", ancients[n-1]}...) @@ -462,7 +461,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) { log.Info("Deep froze chain segment", context...) // Avoid database thrashing with tiny writes - if f.frozen+f.offset-first < freezerBatchLimit { + if f.frozen-first < freezerBatchLimit { backoff = true } } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 5c4b4b4a6a..7a0959a887 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -265,18 +265,18 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str } defer chainDb.Close() - // Get the number of items in old ancient db, i.e. frozen. - frozen, err := chainDb.Ancients() + // Get the number of items in old ancient db. + itemsOfAncient, err := chainDb.Ancients() // If we can't access the freezer or it's empty, abort. - if err != nil || frozen == 0 { + if err != nil || itemsOfAncient == 0 { log.Error("can't access the freezer or it's empty, abort") return errors.New("can't access the freezer or it's empty, abort") } - //If the items in freezer is less than the block amount that we want to reserve, it is not enough, should stop. - if frozen < p.BlockAmountReserved { - log.Error("the number of old blocks is not enough to reserve,", "frozen items", frozen, "the amount specified", p.BlockAmountReserved) + // If the items in freezer is less than the block amount that we want to reserve, it is not enough, should stop. + if itemsOfAncient < p.BlockAmountReserved { + log.Error("the number of old blocks is not enough to reserve,", "ancient items", itemsOfAncient, "the amount specified", p.BlockAmountReserved) return errors.New("the number of old blocks is not enough to reserve") } @@ -292,7 +292,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str } // Get the start BlockNumber for pruning. - startBlockNumber := oldOffSet + frozen - p.BlockAmountReserved + startBlockNumber := oldOffSet + itemsOfAncient - p.BlockAmountReserved // Create new ancientdb backup and record the new and last version of offset in kvDB as well. // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. @@ -310,12 +310,12 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str log.Crit("Failed to write offset into disk", "err", err) } - log.Info("prune info", "old offset", oldOffSet, "frozen items number", frozen, "amount to reserver", p.BlockAmountReserved) + log.Info("prune info", "old offset", oldOffSet, "number of items in ancientDB", itemsOfAncient, "amount to reserve", p.BlockAmountReserved) log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) start := time.Now() // All ancient data after and including startBlockNumber should write into new ancientDB ancient_back. - for blockNumber := startBlockNumber; blockNumber < frozen+oldOffSet; blockNumber++ { + for blockNumber := startBlockNumber; blockNumber < itemsOfAncient+oldOffSet; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(chainDb, blockNumber) block := rawdb.ReadBlock(chainDb, blockHash, blockNumber) receipts := rawdb.ReadRawReceipts(chainDb, blockHash, blockNumber) From 6e13383560bc4cef3628d202d5b340f0d56dc3d0 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Fri, 14 Jan 2022 16:01:33 +0800 Subject: [PATCH 23/26] update the code in all places related to freezer change --- cmd/geth/chaincmd.go | 6 ++-- cmd/geth/dbcmd.go | 16 +++++----- cmd/geth/snapshot.go | 10 +++--- cmd/utils/flags.go | 8 ++--- core/blockchain.go | 10 ++++-- core/rawdb/chain_iterator.go | 13 ++++---- core/rawdb/database.go | 22 ++++++++++--- core/rawdb/freezer.go | 12 +++++++- core/rawdb/table.go | 10 ++++++ core/state/pruner/pruner.go | 60 +++++++++++++++++++++++++++--------- eth/downloader/downloader.go | 4 +-- ethdb/database.go | 6 ++++ 12 files changed, 127 insertions(+), 50 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 9bcbb88762..a30efbbdea 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -458,7 +458,7 @@ func importPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -477,7 +477,7 @@ func exportPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -491,7 +491,7 @@ func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) for _, arg := range ctx.Args() { var header *types.Header if hashish(arg) { diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 483e3ca98a..2ac8b803cb 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -293,7 +293,7 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() return rawdb.InspectDatabase(db, prefix, start) @@ -303,7 +303,7 @@ func ancientInspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, true, false) + db := utils.MakeChainDatabase(ctx, stack, true, true) defer db.Close() return rawdb.AncientInspect(db) } @@ -325,7 +325,7 @@ func dbStats(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() showLeveldbStats(db) @@ -336,7 +336,7 @@ func dbCompact(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() log.Info("Stats before compaction") @@ -360,7 +360,7 @@ func dbGet(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -385,7 +385,7 @@ func dbDelete(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() key, err := hexutil.Decode(ctx.Args().Get(0)) @@ -412,7 +412,7 @@ func dbPut(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, false, false, false) + db := utils.MakeChainDatabase(ctx, stack, false, false) defer db.Close() var ( @@ -446,7 +446,7 @@ func dbDumpTrie(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack, true, false, false) + db := utils.MakeChainDatabase(ctx, stack, true, false) defer db.Close() var ( root []byte diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index d13f9a9150..a8dea6209a 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -183,7 +183,7 @@ It's also usable without snapshot enabled. func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { //The layer of tries trees that keep in memory. TriesInMemory := int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) - chaindb := utils.MakeChainDatabase(ctx, stack, false, true, false) + chaindb := utils.MakeChainDatabase(ctx, stack, false, true) defer chaindb.Close() if !ctx.GlobalBool(utils.CheckSnapshotWithMPT.Name) { @@ -341,7 +341,7 @@ func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, false, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, false, false) pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name), ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) if err != nil { log.Error("Failed to open snapshot tree", "err", err) @@ -370,7 +370,7 @@ func verifyState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -408,7 +408,7 @@ func traverseState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -498,7 +498,7 @@ func traverseRawState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chaindb := utils.MakeChainDatabase(ctx, stack, true, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true, false) headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2e2c432024..58f4798aee 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1775,7 +1775,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DataDirFlag.Name) { // Check if we have an already initialized chain and fall back to // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack, false, false, false) // TODO (MariusVanDerWijden) make this read only + chaindb := MakeChainDatabase(ctx, stack, false, false) // TODO (MariusVanDerWijden) make this read only if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } @@ -1894,7 +1894,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze, isLastOffset bool) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFreeze bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1907,7 +1907,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly, disableFree chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disableFreeze, isLastOffset) + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly, disableFreeze, false) } if err != nil { Fatalf("Could not open database: %v", err) @@ -1937,7 +1937,7 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { // MakeChain creates a chain manager from set command line flags. func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) { var err error - chainDb = MakeChainDatabase(ctx, stack, false, false, false) // TODO(rjl493456442) support read-only database + chainDb = MakeChainDatabase(ctx, stack, false, false) // TODO(rjl493456442) support read-only database config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) if err != nil { Fatalf("%v", err) diff --git a/core/blockchain.go b/core/blockchain.go index 4a7cdf9a54..7bd8de8f4d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -322,9 +322,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par rawdb.InitDatabaseFromFreezer(bc.db) // If ancient database is not empty, reconstruct all missing // indices in the background. - frozen, _ := bc.db.Ancients() + frozen, _ := bc.db.ItemAmountInAncient() if frozen > 0 { - txIndexBlock = frozen + txIndexBlock, _ = bc.db.Ancients() } } if err := bc.loadLastState(); err != nil { @@ -359,7 +359,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } // Ensure that a previous crash in SetHead doesn't leave extra ancients - if frozen, err := bc.db.Ancients(); err == nil && frozen > 0 { + if frozen, err := bc.db.ItemAmountInAncient(); err == nil && frozen > 0 { + frozen, err = bc.db.Ancients() + if err != nil { + return nil, err + } var ( needRewind bool low uint64 diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 4dca06765e..4d7cfee9ed 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -35,17 +35,18 @@ import ( // injects into the database the block hash->number mappings. func InitDatabaseFromFreezer(db ethdb.Database) { // If we can't access the freezer or it's empty, abort - frozen, err := db.Ancients() + frozen, err := db.ItemAmountInAncient() if err != nil || frozen == 0 { return } var ( - batch = db.NewBatch() - start = time.Now() - logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log - hash common.Hash + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log + hash common.Hash + offset, _ = db.AncientOffSet() ) - for i := uint64(0); i < frozen; i++ { + for i := uint64(0) + offset; i < frozen+offset; i++ { // Since the freezer has all data in sequential order on a file, // it would be 'neat' to read more data in one go, and let the // freezerdb return N items (e.g up to 1000 items per go) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 6e488befca..98df559426 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -114,6 +114,11 @@ func (db *nofreezedb) Ancients() (uint64, error) { return 0, errNotSupported } +// Ancients returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) ItemAmountInAncient() (uint64, error) { + return 0, errNotSupported +} + // AncientSize returns an error as we don't have a backing chain freezer. func (db *nofreezedb) AncientSize(kind string) (uint64, error) { return 0, errNotSupported @@ -142,6 +147,10 @@ func (db *nofreezedb) SetDiffStore(diff ethdb.KeyValueStore) { db.diffStore = diff } +func (db *nofreezedb) AncientOffSet() (uint64, error) { + return 0, nil +} + // NewDatabase creates a high level database on top of a given key-value data // store without a freezer moving immutable chain segments into cold storage. func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { @@ -200,7 +209,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } var offset uint64 - //The offset of ancientDB should be handled differently in different scenarios + // The offset of ancientDB should be handled differently in different scenarios. if isLastOffset { offset = ReadOffSetOfLastAncientFreezer(db) } else { @@ -362,10 +371,15 @@ func AncientInspect(db ethdb.Database) error { offset := counter(ReadOffSetOfCurrentAncientFreezer(db)) // Get number of ancient rows inside the freezer. ancients := counter(0) - if count, err := db.Ancients(); err == nil { + if count, err := db.ItemAmountInAncient(); err == nil { ancients = counter(count) } - endNumber := offset + ancients - 1 + var endNumber counter + if offset+ancients <= 0 { + endNumber = 0 + } else { + endNumber = offset + ancients - 1 + } stats := [][]string{ {"Offset/StartBlockNumber", "Offset/StartBlockNumber of ancientDB", offset.String()}, {"Amount of remained items in AncientStore", "Remaining items of ancientDB", ancients.String()}, @@ -512,7 +526,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { } // Get number of ancient rows inside the freezer ancients := counter(0) - if count, err := db.Ancients(); err == nil { + if count, err := db.ItemAmountInAncient(); err == nil { ancients = counter(count) } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a98120fed8..d16f64e83c 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -181,9 +181,19 @@ func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) { // Ancients returns the length of the frozen items. func (f *freezer) Ancients() (uint64, error) { + return atomic.LoadUint64(&f.frozen), nil +} + +// ItemAmountInAncient returns the actual length of current ancientDB. +func (f *freezer) ItemAmountInAncient() (uint64, error) { return atomic.LoadUint64(&f.frozen) - atomic.LoadUint64(&f.offset), nil } +// AncientOffSet returns the offset of current ancientDB. +func (f *freezer) AncientOffSet() (uint64, error) { + return atomic.LoadUint64(&f.offset), nil +} + // AncientSize returns the ancient size of the specified category. func (f *freezer) AncientSize(kind string) (uint64, error) { if table := f.tables[kind]; table != nil { @@ -251,7 +261,7 @@ func (f *freezer) TruncateAncients(items uint64) error { return nil } for _, table := range f.tables { - if err := table.truncate(items); err != nil { + if err := table.truncate(items - f.offset); err != nil { return err } } diff --git a/core/rawdb/table.go b/core/rawdb/table.go index a27f5f8ed7..06049f480b 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -68,6 +68,16 @@ func (t *table) Ancients() (uint64, error) { return t.db.Ancients() } +// ItemAmountInAncient returns the actual length of current ancientDB. +func (t *table) ItemAmountInAncient() (uint64, error) { + return t.db.ItemAmountInAncient() +} + +// AncientOffSet returns the offset of current ancientDB. +func (t *table) AncientOffSet() (uint64, error) { + return t.db.AncientOffSet() +} + // AncientSize is a noop passthrough that just forwards the request to the underlying // database. func (t *table) AncientSize(kind string) (uint64, error) { diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 7a0959a887..699d2861cc 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -28,6 +28,8 @@ import ( "strings" "time" + "github.com/prometheus/tsdb/fileutil" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/rawdb" @@ -256,17 +258,18 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta } func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace string, readonly, interrupt bool) error { - - //Open old db wrapper. + // Open old db wrapper. chainDb, err := p.node.OpenDatabaseWithFreezer(name, cache, handles, p.oldAncientPath, namespace, readonly, true, interrupt) if err != nil { log.Error("Failed to open ancient database", "err=", err) return err } defer chainDb.Close() + log.Info("chainDB opened successfully") // Get the number of items in old ancient db. - itemsOfAncient, err := chainDb.Ancients() + itemsOfAncient, err := chainDb.ItemAmountInAncient() + log.Info("the number of items in ancientDB is ", "itemsOfAncient", itemsOfAncient) // If we can't access the freezer or it's empty, abort. if err != nil || itemsOfAncient == 0 { @@ -282,17 +285,19 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str var oldOffSet uint64 if interrupt { - //The interrupt scecario within this function is specific for old and new ancientDB exsisted concurrently, - //should use last version of offset for oldAncientDB, because current offset is - //acutally of the new ancientDB_Backup, but what we want is the offset of ancientDB being backup. + // The interrupt scecario within this function is specific for old and new ancientDB exsisted concurrently, + // should use last version of offset for oldAncientDB, because current offset is + // actually of the new ancientDB_Backup, but what we want is the offset of ancientDB being backup. oldOffSet = rawdb.ReadOffSetOfLastAncientFreezer(chainDb) } else { - //Using current version of ancientDB for oldOffSet because the db for backup is current version. + // Using current version of ancientDB for oldOffSet because the db for backup is current version. oldOffSet = rawdb.ReadOffSetOfCurrentAncientFreezer(chainDb) } + log.Info("the oldOffSet is ", "oldOffSet", oldOffSet) // Get the start BlockNumber for pruning. startBlockNumber := oldOffSet + itemsOfAncient - p.BlockAmountReserved + log.Info("new offset/new startBlockNumber is ", "new offset", startBlockNumber) // Create new ancientdb backup and record the new and last version of offset in kvDB as well. // For every round, newoffset actually equals to the startBlockNumber in ancient backup db. @@ -310,6 +315,13 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str log.Crit("Failed to write offset into disk", "err", err) } + // It's guaranteed that the old/new offsets are updated as well as the new ancientDB are created if this flock exist. + lock, _, err := fileutil.Flock(filepath.Join(p.newAncientPath, "PRUNEFLOCKBACK")) + if err != nil { + log.Error("file lock error", "err", err) + return err + } + log.Info("prune info", "old offset", oldOffSet, "number of items in ancientDB", itemsOfAncient, "amount to reserve", p.BlockAmountReserved) log.Info("new offset/new startBlockNumber recorded successfully ", "new offset", startBlockNumber) @@ -334,6 +346,7 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str start = time.Now() } } + lock.Release() log.Info("block back up done", "current start blockNumber in ancientDB", startBlockNumber) return nil } @@ -352,29 +365,48 @@ func (p *BlockPruner) BlockPruneBackUp(name string, cache, handles int, namespac } func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, namespace string, readonly bool) error { - - newExist, err := CheckAncientDbExist(p.newAncientPath) + log.Info("RecoverInterruption for block prune") + newExist, err := CheckFileExist(p.newAncientPath) if err != nil { log.Error("newAncientDb path error") return err } if newExist { - // Indicating old and new ancientDB existed concurrently. + log.Info("New ancientDB_backup existed in interruption scenario") + var flockOfAncientBack bool + if exist, err := CheckFileExist(filepath.Join(p.newAncientPath, "PRUNEFLOCKBACK")); err != nil { + log.Error("Failed to check flock of ancientDB_Back %v", err) + return err + } else { + flockOfAncientBack = exist + } + // Indicating both old and new ancientDB existed concurrently. // Delete directly for the new ancientdb to prune from start, e.g.: path ../chaindb/ancient_backup if err := os.RemoveAll(p.newAncientPath); err != nil { log.Error("Failed to remove old ancient directory %v", err) return err } - if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, true); err != nil { - log.Error("Failed to prune") - return err + if flockOfAncientBack { + // Indicating the oldOffset/newOffset have already been updated. + if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, true); err != nil { + log.Error("Failed to prune") + return err + } + } else { + // Indicating the flock did not exist and the new offset did not be updated, so just handle this case as usual. + if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, false); err != nil { + log.Error("Failed to prune") + return err + } } + if err := p.AncientDbReplacer(); err != nil { log.Error("Failed to replace ancientDB") return err } } else { + log.Info("New ancientDB_backup did not exist in interruption scenario") // Indicating new ancientDB even did not be created, just prune starting at backup from startBlockNumber as usual, // in this case, the new offset have not been written into kvDB. if err := p.BlockPruneBackUp(name, cache, handles, namespace, readonly, false); err != nil { @@ -390,7 +422,7 @@ func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, names return nil } -func CheckAncientDbExist(path string) (bool, error) { +func CheckFileExist(path string) (bool, error) { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { //Indicating the file didn't exist. diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 0009f41786..e1225e7a1c 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -599,10 +599,10 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.ancientLimit = 0 } frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. - + itemAmountInAncient, _ := d.stateDB.ItemAmountInAncient() // If a part of blockchain data has already been written into active store, // disable the ancient style insertion explicitly. - if origin >= frozen && frozen != 0 { + if origin >= frozen && itemAmountInAncient != 0 { d.ancientLimit = 0 log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) } else if d.ancientLimit > 0 { diff --git a/ethdb/database.go b/ethdb/database.go index 40d0e01d57..7744a62cf6 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -81,6 +81,12 @@ type AncientReader interface { // AncientSize returns the ancient size of the specified category. AncientSize(kind string) (uint64, error) + + // ItemAmountInAncient returns the actual length of current ancientDB. + ItemAmountInAncient() (uint64, error) + + // AncientOffSet returns the offset of current ancientDB. + AncientOffSet() (uint64, error) } // AncientWriter contains the methods required to write to immutable ancient data. From 29279f22a6b6e6af23bb251f44860ee1cd8962a8 Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Tue, 18 Jan 2022 17:00:43 +0800 Subject: [PATCH 24/26] addressed comments from dylan --- cmd/geth/pruneblock_test.go | 2 +- cmd/geth/snapshot.go | 14 +++++--------- core/rawdb/database.go | 11 +++++++---- core/state/pruner/pruner.go | 6 +++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 3349828a35..5122de7db1 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -86,7 +86,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 defer node.Close() //Initialize a block pruner for pruning, only remain amountReserved blocks backward. - testBlockPruner, err := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountReserved) + testBlockPruner := pruner.NewBlockPruner(db, node, oldAncientPath, newAncientPath, amountReserved) if err != nil { t.Fatalf("failed to make new blockpruner: %v", err) } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index a8dea6209a..20920a0f94 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -259,7 +259,7 @@ func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { } } else { if len(layers) > 0 { - log.Info("Selecting bottom-most difflayer as the pruning target", "root", targetRoot, "height", headHeader.Number.Uint64()-127) + log.Info("Selecting bottom-most difflayer as the pruning target", "root", targetRoot, "height", headHeader.Number.Uint64()-uint64(len(layers)-1)) } else { log.Info("Selecting user-specified state as the pruning target", "root", targetRoot) } @@ -282,16 +282,12 @@ func pruneBlock(ctx *cli.Context) error { } path, _ := filepath.Split(oldAncientPath) - if path != "" { - newAncientPath = filepath.Join(path, "ancient_back") - } else { + if path == "" { return errors.New("prune failed, did not specify the AncientPath") } + newAncientPath = filepath.Join(path, "ancient_back") - blockpruner, err := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, blockAmountReserved) - if err != nil { - return err - } + blockpruner := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, blockAmountReserved) lock, exist, err := fileutil.Flock(filepath.Join(oldAncientPath, "PRUNEFLOCK")) if err != nil { @@ -299,12 +295,12 @@ func pruneBlock(ctx *cli.Context) error { return err } if exist { + defer lock.Release() log.Info("file lock existed, waiting for prune recovery and continue", "err", err) if err := blockpruner.RecoverInterruption("chaindata", config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { log.Error("Pruning failed", "err", err) return err } - lock.Release() log.Info("Block prune successfully") return nil } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 98df559426..579ef22983 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -189,9 +189,9 @@ func WriteOffSetOfLastAncientFreezer(db ethdb.KeyValueWriter, offset uint64) { // NewFreezerDb only create a freezer without statedb. func NewFreezerDb(db ethdb.KeyValueStore, frz, namespace string, readonly bool, newOffSet uint64) (*freezer, error) { // Create the idle freezer instance, this operation should be atomic to avoid mismatch between offset and acientDB. - frdb, errors := newFreezer(frz, namespace, readonly) - if errors != nil { - return nil, errors + frdb, err := newFreezer(frz, namespace, readonly) + if err != nil { + return nil, err } frdb.offset = newOffSet frdb.frozen += newOffSet @@ -371,7 +371,10 @@ func AncientInspect(db ethdb.Database) error { offset := counter(ReadOffSetOfCurrentAncientFreezer(db)) // Get number of ancient rows inside the freezer. ancients := counter(0) - if count, err := db.ItemAmountInAncient(); err == nil { + if count, err := db.ItemAmountInAncient(); err != nil { + log.Error("failed to get the items amount in ancientDB", "err", err) + return err + } else { ancients = counter(count) } var endNumber counter diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 699d2861cc..855cb599d8 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -129,14 +129,14 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, trie }, nil } -func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockAmountReserved uint64) (*BlockPruner, error) { +func NewBlockPruner(db ethdb.Database, n *node.Node, oldAncientPath, newAncientPath string, BlockAmountReserved uint64) *BlockPruner { return &BlockPruner{ db: db, oldAncientPath: oldAncientPath, newAncientPath: newAncientPath, node: n, BlockAmountReserved: BlockAmountReserved, - }, nil + } } func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error { @@ -425,7 +425,7 @@ func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, names func CheckFileExist(path string) (bool, error) { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { - //Indicating the file didn't exist. + // Indicating the file didn't exist. return false, nil } return true, err From dbfc2316278bcb368774640148052558805cd33c Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Wed, 19 Jan 2022 11:41:23 +0800 Subject: [PATCH 25/26] update the logic for backup block difficulty --- cmd/geth/pruneblock_test.go | 12 +++++++----- core/state/pruner/pruner.go | 5 +---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index 5122de7db1..bfa14c836d 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -104,7 +104,11 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 for blockNumber := startBlockNumber; blockNumber < startBlockNumber+amountReserved; blockNumber++ { blockHash := rawdb.ReadCanonicalHash(dbBack, blockNumber) block := rawdb.ReadBlock(dbBack, blockHash, blockNumber) - if reflect.DeepEqual(block, blockList[blockNumber-startBlockNumber]) { + + if block.Hash() != blockHash { + t.Fatalf("block data did not match between oldDb and backupDb") + } + if blockList[blockNumber-startBlockNumber].Hash() != blockHash { t.Fatalf("block data did not match between oldDb and backupDb") } @@ -117,8 +121,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 if td == nil { t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor) } - externTd := new(big.Int).Add(block.Difficulty(), td) - if reflect.DeepEqual(externTd, externTdList[blockNumber-startBlockNumber]) { + if !reflect.DeepEqual(td, externTdList[blockNumber-startBlockNumber]) { t.Fatalf("externTd did not match between oldDb and backupDb") } } @@ -198,8 +201,7 @@ func BlockchainCreator(t *testing.T, chaindbPath, AncientPath string, blockRemai if td == nil { t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor) } - externTd := new(big.Int).Add(block.Difficulty(), td) - externTdList = append(externTdList, externTd) + externTdList = append(externTdList, td) } return db, blocks, blockList, receiptsList, externTdList, startBlockNumber, blockchain diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 855cb599d8..c1d5f85d9a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -22,7 +22,6 @@ import ( "errors" "fmt" "math" - "math/big" "os" "path/filepath" "strings" @@ -336,10 +335,8 @@ func (p *BlockPruner) backUpOldDb(name string, cache, handles int, namespace str if td == nil { return consensus.ErrUnknownAncestor } - externTd := new(big.Int).Add(block.Difficulty(), td) - // Write into new ancient_back db. - rawdb.WriteAncientBlock(frdbBack, block, receipts, externTd) + rawdb.WriteAncientBlock(frdbBack, block, receipts, td) // Print the log every 5s for better trace. if common.PrettyDuration(time.Since(start)) > common.PrettyDuration(5*time.Second) { log.Info("block backup process running successfully", "current blockNumber for backup", blockNumber) From 37f2e897c3b181b2af7d8927a27731c8baacab1f Mon Sep 17 00:00:00 2001 From: Mercybudda <708967537@qq.com> Date: Wed, 19 Jan 2022 12:12:00 +0800 Subject: [PATCH 26/26] addressed comments from dylan --- cmd/geth/pruneblock_test.go | 3 +-- core/rawdb/chain_iterator.go | 10 +++++----- core/rawdb/database.go | 4 ++-- core/rawdb/freezer.go | 4 ++-- core/rawdb/table.go | 2 +- core/state/pruner/pruner.go | 7 +++---- ethdb/database.go | 2 +- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cmd/geth/pruneblock_test.go b/cmd/geth/pruneblock_test.go index bfa14c836d..7d78f444fa 100644 --- a/cmd/geth/pruneblock_test.go +++ b/cmd/geth/pruneblock_test.go @@ -24,7 +24,6 @@ import ( "math/big" "os" "path/filepath" - "reflect" "testing" "time" @@ -121,7 +120,7 @@ func testOfflineBlockPruneWithAmountReserved(t *testing.T, amountReserved uint64 if td == nil { t.Fatalf("Failed to ReadTd: %v", consensus.ErrUnknownAncestor) } - if !reflect.DeepEqual(td, externTdList[blockNumber-startBlockNumber]) { + if td.Cmp(externTdList[blockNumber-startBlockNumber]) != 0 { t.Fatalf("externTd did not match between oldDb and backupDb") } } diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 4d7cfee9ed..22d5188e91 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -40,11 +40,11 @@ func InitDatabaseFromFreezer(db ethdb.Database) { return } var ( - batch = db.NewBatch() - start = time.Now() - logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log - hash common.Hash - offset, _ = db.AncientOffSet() + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log + hash common.Hash + offset = db.AncientOffSet() ) for i := uint64(0) + offset; i < frozen+offset; i++ { // Since the freezer has all data in sequential order on a file, diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 579ef22983..3342b36f36 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -147,8 +147,8 @@ func (db *nofreezedb) SetDiffStore(diff ethdb.KeyValueStore) { db.diffStore = diff } -func (db *nofreezedb) AncientOffSet() (uint64, error) { - return 0, nil +func (db *nofreezedb) AncientOffSet() uint64 { + return 0 } // NewDatabase creates a high level database on top of a given key-value data diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index d16f64e83c..92dfc9604a 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -190,8 +190,8 @@ func (f *freezer) ItemAmountInAncient() (uint64, error) { } // AncientOffSet returns the offset of current ancientDB. -func (f *freezer) AncientOffSet() (uint64, error) { - return atomic.LoadUint64(&f.offset), nil +func (f *freezer) AncientOffSet() uint64 { + return atomic.LoadUint64(&f.offset) } // AncientSize returns the ancient size of the specified category. diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 06049f480b..0dcaf8cfb0 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -74,7 +74,7 @@ func (t *table) ItemAmountInAncient() (uint64, error) { } // AncientOffSet returns the offset of current ancientDB. -func (t *table) AncientOffSet() (uint64, error) { +func (t *table) AncientOffSet() uint64 { return t.db.AncientOffSet() } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index c1d5f85d9a..5b070f3afa 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -371,13 +371,12 @@ func (p *BlockPruner) RecoverInterruption(name string, cache, handles int, names if newExist { log.Info("New ancientDB_backup existed in interruption scenario") - var flockOfAncientBack bool - if exist, err := CheckFileExist(filepath.Join(p.newAncientPath, "PRUNEFLOCKBACK")); err != nil { + flockOfAncientBack, err := CheckFileExist(filepath.Join(p.newAncientPath, "PRUNEFLOCKBACK")) + if err != nil { log.Error("Failed to check flock of ancientDB_Back %v", err) return err - } else { - flockOfAncientBack = exist } + // Indicating both old and new ancientDB existed concurrently. // Delete directly for the new ancientdb to prune from start, e.g.: path ../chaindb/ancient_backup if err := os.RemoveAll(p.newAncientPath); err != nil { diff --git a/ethdb/database.go b/ethdb/database.go index 7744a62cf6..e437817a20 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -86,7 +86,7 @@ type AncientReader interface { ItemAmountInAncient() (uint64, error) // AncientOffSet returns the offset of current ancientDB. - AncientOffSet() (uint64, error) + AncientOffSet() uint64 } // AncientWriter contains the methods required to write to immutable ancient data.