From 5dc0bd63d94a35d7ca36fd348243bb7904f2428a Mon Sep 17 00:00:00 2001 From: Harry Ngo <17699212+huyngopt1994@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:49:40 +0700 Subject: [PATCH] Change ancient chain segments from root ancient to sub folders (#572) * cmd, core, ethdb, node: rework ancient store folder reference by https://github.com/ethereum/go-ethereum/commit/e44d6551c3c872584722c366c863381f7e91df91 --- cmd/ronin/dbcmd.go | 51 +++++++++--------- cmd/utils/flags.go | 2 +- core/rawdb/accessors_chain.go | 24 ++++----- core/rawdb/ancient_scheme.go | 89 ++++++++++++++++++++++++++++++++ core/rawdb/chain_freezer.go | 10 ++-- core/rawdb/chain_iterator.go | 2 +- core/rawdb/database.go | 35 +++++++++++-- core/rawdb/freezer.go | 8 --- core/rawdb/freezer_table.go | 6 +-- core/rawdb/freezer_table_test.go | 2 +- core/rawdb/schema.go | 27 ---------- ethdb/database.go | 1 - node/node.go | 22 ++++---- 13 files changed, 179 insertions(+), 100 deletions(-) create mode 100644 core/rawdb/ancient_scheme.go diff --git a/cmd/ronin/dbcmd.go b/cmd/ronin/dbcmd.go index ee25dad8ed..c4793b25e3 100644 --- a/cmd/ronin/dbcmd.go +++ b/cmd/ronin/dbcmd.go @@ -23,7 +23,6 @@ import ( "os" "os/signal" "path/filepath" - "sort" "strconv" "strings" "syscall" @@ -197,8 +196,8 @@ WARNING: This is a low-level operation which may cause database corruption!`, dbDumpFreezerIndex = &cli.Command{ Action: freezerInspect, Name: "freezer-index", - Usage: "Dump out the index of a given freezer type", - ArgsUsage: " ", + Usage: "Dump out the index of a specific freezer table", + ArgsUsage: " ", Flags: []cli.Flag{ utils.DataDirFlag, utils.DBEngineFlag, @@ -311,7 +310,7 @@ func inspect(ctx *cli.Context) error { start []byte ) if ctx.NArg() > 2 { - return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + return fmt.Errorf("max 2 arguments: %v", ctx.Command.ArgsUsage) } if ctx.NArg() >= 1 { if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { @@ -519,25 +518,19 @@ func dbDumpTrie(ctx *cli.Context) error { func freezerInspect(ctx *cli.Context) error { var ( - start, end int64 - disableSnappy bool - err error + start, end int64 + err error ) - if ctx.NArg() < 3 { + if ctx.NArg() < 4 { return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) } - kind := ctx.Args().Get(0) - if noSnap, ok := rawdb.FreezerNoSnappy[kind]; !ok { - var options []string - for opt := range rawdb.FreezerNoSnappy { - options = append(options, opt) - } - sort.Strings(options) - return fmt.Errorf("Could read freezer-type '%v'. Available options: %v", kind, options) - } else { - disableSnappy = noSnap - } - if start, err = strconv.ParseInt(ctx.Args().Get(1), 10, 64); err != nil { + + var ( + freezerType = ctx.Args().Get(0) + tableType = ctx.Args().Get(1) + ) + + if start, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { log.Info("Could read start-param", "error", err) return err } @@ -545,16 +538,20 @@ func freezerInspect(ctx *cli.Context) error { log.Info("Could read count param", "error", err) return err } + stack, _ := makeConfigNode(ctx) defer stack.Close() - path := filepath.Join(stack.ResolvePath("chaindata"), "ancient") - log.Info("Opening freezer", "location", path, "name", kind) - if f, err := rawdb.NewFreezerTable(path, kind, disableSnappy); err != nil { - return err - } else { - f.DumpIndex(start, end) + // Open the Freezer Database with mode read-only + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + ancient, err := db.AncientDatadir() + if err != nil { + log.Info("Failed to retrive ancient root", "err", err) } - return nil + + return rawdb.InspectFreezerTable(ancient, freezerType, tableType, start, end) + } // ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8ff1a5cc80..ea1a1b65f0 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -89,7 +89,7 @@ var ( } AncientFlag = &flags.DirectoryFlag{ Name: "datadir.ancient", - Usage: "Data directory for ancient chain segments (default = inside chaindata)", + Usage: "Root directory for ancient data (default = inside chaindata)", Category: flags.EthCategory, } MinFreeDiskSpaceFlag = &flags.DirectoryFlag{ diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 7daa44fad1..f4dec70d6b 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -37,7 +37,7 @@ import ( func ReadCanonicalHash(db ethdb.Reader, number uint64) common.Hash { var data []byte db.ReadAncients(func(reader ethdb.AncientReaderOp) error { - data, _ = reader.Ancient(freezerHashTable, number) + data, _ = reader.Ancient(chainFreezerHashTable, number) if len(data) == 0 { // Get it by hash from leveldb data, _ = db.Get(headerHashKey(number)) @@ -304,7 +304,7 @@ func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValu // First try to look up the data in ancient database. Extra hash // comparison is necessary since ancient database only maintains // the canonical data. - data, _ = reader.Ancient(freezerHeaderTable, number) + data, _ = reader.Ancient(chainFreezerHeaderTable, number) if len(data) > 0 && crypto.Keccak256Hash(data) == hash { return nil } @@ -380,7 +380,7 @@ func deleteHeaderWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number // isCanon is an internal utility method, to check whether the given number/hash // is part of the ancient (canon) set. func isCanon(reader ethdb.AncientReaderOp, number uint64, hash common.Hash) bool { - h, err := reader.Ancient(freezerHashTable, number) + h, err := reader.Ancient(chainFreezerHashTable, number) if err != nil { return false } @@ -396,7 +396,7 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { - data, _ = reader.Ancient(freezerBodiesTable, number) + data, _ = reader.Ancient(chainFreezerBodiesTable, number) return nil } // If not, try reading from leveldb @@ -411,7 +411,7 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { var data []byte db.ReadAncients(func(reader ethdb.AncientReaderOp) error { - data, _ = reader.Ancient(freezerBodiesTable, number) + data, _ = reader.Ancient(chainFreezerBodiesTable, number) if len(data) > 0 { return nil } @@ -501,7 +501,7 @@ func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { - data, _ = reader.Ancient(freezerDifficultyTable, number) + data, _ = reader.Ancient(chainFreezerDifficultyTable, number) return nil } // If not, try reading from leveldb @@ -561,7 +561,7 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa db.ReadAncients(func(reader ethdb.AncientReaderOp) error { // Check if the data is in ancients if isCanon(reader, number, hash) { - data, _ = reader.Ancient(freezerReceiptTable, number) + data, _ = reader.Ancient(chainFreezerReceiptTable, number) return nil } // If not, try reading from leveldb @@ -793,19 +793,19 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage, td *big.Int) error { num := block.NumberU64() - if err := op.AppendRaw(freezerHashTable, num, block.Hash().Bytes()); err != nil { + if err := op.AppendRaw(chainFreezerHashTable, num, block.Hash().Bytes()); err != nil { return fmt.Errorf("can't add block %d hash: %v", num, err) } - if err := op.Append(freezerHeaderTable, num, header); err != nil { + if err := op.Append(chainFreezerHeaderTable, num, header); err != nil { return fmt.Errorf("can't append block header %d: %v", num, err) } - if err := op.Append(freezerBodiesTable, num, block.Body()); err != nil { + if err := op.Append(chainFreezerBodiesTable, num, block.Body()); err != nil { return fmt.Errorf("can't append block body %d: %v", num, err) } - if err := op.Append(freezerReceiptTable, num, receipts); err != nil { + if err := op.Append(chainFreezerReceiptTable, num, receipts); err != nil { return fmt.Errorf("can't append block %d receipts: %v", num, err) } - if err := op.Append(freezerDifficultyTable, num, td); err != nil { + if err := op.Append(chainFreezerDifficultyTable, num, td); err != nil { return fmt.Errorf("can't append block %d total difficulty: %v", num, err) } return nil diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go new file mode 100644 index 0000000000..627a611998 --- /dev/null +++ b/core/rawdb/ancient_scheme.go @@ -0,0 +1,89 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import "fmt" + +// The list of table names of chain freezer. (headers, hashes, bodies, difficulties) + +const ( + // chainFreezerHeaderTable indicates the name of the freezer header table. + chainFreezerHeaderTable = "headers" + + // chainFreezerHashTable indicates the name of the freezer canonical hash table. + chainFreezerHashTable = "hashes" + + // chainFreezerBodiesTable indicates the name of the freezer block body table. + chainFreezerBodiesTable = "bodies" + + // chainFreezerReceiptTable indicates the name of the freezer receipts table. + chainFreezerReceiptTable = "receipts" + + // chainFreezerDifficultyTable indicates the name of the freezer total difficulty table. + chainFreezerDifficultyTable = "diffs" +) + +// chainFreezerNoSnappy configures whether compression is disabled for the ancient-tables. +// Hashes and difficulties don't compress well. +var chainFreezerNoSnappy = map[string]bool{ + chainFreezerHeaderTable: false, + chainFreezerHashTable: true, + chainFreezerBodiesTable: false, + chainFreezerReceiptTable: false, + chainFreezerDifficultyTable: true, +} + +// The list of identifiers of ancient stores. It can split more in the futures. +var ( + chainFreezerName = "chain" // the folder name of chain segment ancient store. +) + +// freezers the collections of all builtin freezers. +var freezers = []string{chainFreezerName} + +// InspectFreezerTable dumps out the index of a specific freezer table. The passed +// ancient indicates the path of root ancient directory where the chain freezer can +// be opened. Start and end specifiy the range for dumping out indexes. +// Note this function can only used for debugging purpose. +func InspectFreezerTable(ancient string, freezerName string, tableName string, start, end int64) error { + var ( + path string + tables map[string]bool + ) + + switch freezerName { + case chainFreezerName: + path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy + default: + return fmt.Errorf("unknown freezer, supported ones: %v", freezers) + } + noSnappy, exit := tables[tableName] + if !exit { + // If the tableName is not exit in the tables, return an error. + var names []string + for name := range tables { + names = append(names, name) + } + return fmt.Errorf("unknown table name, supported ones: %v", names) + } + table, err := newFreezerTable(path, tableName, noSnappy) + if err != nil { + return err + } + table.dumpIndexStdout(start, end) + return nil +} diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index ca24111cf3..a0d71654f3 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -274,19 +274,19 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash } // Write to the batch. - if err := op.AppendRaw(freezerHashTable, number, hash[:]); err != nil { + if err := op.AppendRaw(chainFreezerHashTable, number, hash[:]); err != nil { return fmt.Errorf("can't write hash to freezer: %v", err) } - if err := op.AppendRaw(freezerHeaderTable, number, header); err != nil { + if err := op.AppendRaw(chainFreezerHeaderTable, number, header); err != nil { return fmt.Errorf("can't write header to freezer: %v", err) } - if err := op.AppendRaw(freezerBodiesTable, number, body); err != nil { + if err := op.AppendRaw(chainFreezerBodiesTable, number, body); err != nil { return fmt.Errorf("can't write body to freezer: %v", err) } - if err := op.AppendRaw(freezerReceiptTable, number, receipts); err != nil { + if err := op.AppendRaw(chainFreezerReceiptTable, number, receipts); err != nil { return fmt.Errorf("can't write receipts to freezer: %v", err) } - if err := op.AppendRaw(freezerDifficultyTable, number, td); err != nil { + if err := op.AppendRaw(chainFreezerDifficultyTable, number, td); err != nil { return fmt.Errorf("can't write td to freezer: %v", err) } diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index daee721594..f30866fe97 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -50,7 +50,7 @@ func InitDatabaseFromFreezer(db ethdb.Database) { if i+count > frozen { count = frozen - i } - data, err := db.AncientRange(freezerHashTable, i, count, 32*count) + data, err := db.AncientRange(chainFreezerHashTable, i, count, 32*count) if err != nil { log.Crit("Failed to init database from freezer", "err", err) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 82f8872dfa..749ea0c381 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "sync/atomic" "time" @@ -154,12 +155,36 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { return &nofreezedb{KeyValueStore: db} } +// resolveChainFreezerDir is a helper function which resolves the absolute path +// of chain freezer by considering backward compatibility. +func resolveChainFreezerDir(ancient string) string { + // Check if the chain freezer is already present in the specified + // sub folder, if not then two possiblities + // - chain freezer is not initialized + // - it's legacy location, chain freezer is present in the root ancient folder + + freezer := path.Join(ancient, chainFreezerName) + if !common.FileExist(freezer) { + if !common.FileExist(ancient) { + // The entire ancient store is not initialized, still use the sub + // folder for initialization. + } else { + // Ancient root is already initialized, then we hold the assumption + // that chain freezer is also initialized and located in root folder. + // In this case fallback to legacy location. + freezer = ancient + log.Info("Found legacy ancient chain path", "location", ancient) + } + } + return freezer +} + // 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, ancient string, namespace string, readonly bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newChainFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy) + frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly, freezerTableSize, chainFreezerNoSnappy) if err != nil { return nil, err } @@ -190,7 +215,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st // 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) + frgenesis, err := frdb.Ancient(chainFreezerHashTable, 0) if err != nil { return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) } else if !bytes.Equal(kvgenesis, frgenesis) { @@ -236,7 +261,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st }() } return &freezerdb{ - ancientRoot: freezer, // o.AncientsDirectory + ancientRoot: ancient, // o.AncientsDirectory KeyValueStore: db, AncientStore: frdb, }, nil @@ -518,7 +543,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { } // Inspect append-only file store then. ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize} - for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} { + for i, category := range []string{chainFreezerHeaderTable, chainFreezerBodiesTable, chainFreezerReceiptTable, chainFreezerHashTable, chainFreezerDifficultyTable} { if size, err := db.AncientSize(category); err == nil { *ancientSizes[i] += common.StorageSize(size) total += common.StorageSize(size) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a0963ac1a7..064bcf2167 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -70,8 +70,6 @@ type Freezer struct { frozen uint64 // Number of blocks already frozen threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) - datadir string // Path of root directory of ancient store - // This lock synchronizes writers and the truncate operation, as well as // the "atomic" (batched) read operations. writeLock sync.RWMutex @@ -121,7 +119,6 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui instanceLock: lock, trigger: make(chan chan struct{}), quit: make(chan struct{}), - datadir: datadir, } // Create the tables. @@ -318,8 +315,3 @@ func (f *Freezer) repair() error { atomic.StoreUint64(&f.frozen, min) return nil } - -// AncientDatadir returns the root directory path of the ancient store. -func (f *Freezer) AncientDatadir() (string, error) { - return f.datadir, nil -} diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 22405cf9b4..d4ae496b5c 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -118,8 +118,8 @@ type freezerTable struct { lock sync.RWMutex // Mutex protecting the data file descriptors } -// NewFreezerTable opens the given path as a freezer table. -func NewFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) { +// newFreezerTable opens the given path as a freezer table. +func newFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) { return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, freezerTableSize, disableSnappy) } @@ -710,7 +710,7 @@ func (t *freezerTable) Sync() error { // DumpIndex is a debug print utility function, mainly for testing. It can also // be used to analyse a live freezer table index. -func (t *freezerTable) DumpIndex(start, stop int64) { +func (t *freezerTable) dumpIndexStdout(start, stop int64) { t.dumpIndex(os.Stdout, start, stop) } diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 803809b520..923ba8d0cc 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -732,7 +732,7 @@ func TestSequentialRead(t *testing.T) { } // Write 15 bytes 30 times writeChunks(t, f, 30, 15) - f.DumpIndex(0, 30) + f.dumpIndexStdout(0, 30) f.Close() } { // Open it, iterate, verify iteration diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 4b767f87a1..f6648717de 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -112,33 +112,6 @@ var ( preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) ) -const ( - // freezerHeaderTable indicates the name of the freezer header table. - freezerHeaderTable = "headers" - - // freezerHashTable indicates the name of the freezer canonical hash table. - freezerHashTable = "hashes" - - // freezerBodiesTable indicates the name of the freezer block body table. - freezerBodiesTable = "bodies" - - // freezerReceiptTable indicates the name of the freezer receipts table. - freezerReceiptTable = "receipts" - - // freezerDifficultyTable indicates the name of the freezer total difficulty table. - freezerDifficultyTable = "diffs" -) - -// FreezerNoSnappy configures whether compression is disabled for the ancient-tables. -// Hashes and difficulties don't compress well. -var FreezerNoSnappy = map[string]bool{ - freezerHeaderTable: false, - freezerHashTable: true, - freezerBodiesTable: false, - freezerReceiptTable: false, - freezerDifficultyTable: true, -} - // LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary // fields. type LegacyTxLookupEntry struct { diff --git a/ethdb/database.go b/ethdb/database.go index 913cfc7880..3ddfbbac08 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -148,7 +148,6 @@ type Writer interface { type AncientStore interface { AncientReader AncientWriter - AncientStater io.Closer } diff --git a/node/node.go b/node/node.go index 2cf85519f6..b7000b339c 100644 --- a/node/node.go +++ b/node/node.go @@ -602,7 +602,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r // also attaching a chain freezer to it that moves ancient chain data from the // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. -func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly bool) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -613,17 +613,10 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, 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.Open(rawdb.OpenOptions{ Type: n.config.DBEngine, Directory: n.ResolvePath(name), - AncientsDirectory: freezer, + AncientsDirectory: n.ResolveAncient(name, ancient), Namespace: namespace, Cache: cache, Handles: handles, @@ -637,6 +630,17 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, return db, err } +// ResolveAncient returns the absolute path of the root ancient directory. +func (n *Node) ResolveAncient(name string, ancient string) string { + switch { + case ancient == "": // Use the default ancient directory + ancient = filepath.Join(n.ResolvePath(name), "ancient") + case !filepath.IsAbs(ancient): // ancient is relative path to the instance directory + ancient = n.ResolvePath(ancient) + } + return ancient +} + // ResolvePath returns the absolute path of a resource in the instance directory. func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x)