diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index bd1e72612a02..4e968523d4f9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1674,6 +1674,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheSnapshotFlag.Name) { cfg.SnapshotCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheSnapshotFlag.Name) / 100 } + if journal := ctx.String(CacheTrieJournalFlag.Name); journal != "" { + cfg.TrieJournal = stack.ResolvePath(journal) + } if ctx.IsSet(CacheLogSizeFlag.Name) { cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name) } @@ -2179,6 +2182,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh StateScheme: scheme, StateHistory: ctx.Uint64(StateHistoryFlag.Name), } + if journal := ctx.String(CacheTrieJournalFlag.Name); journal != "" { + cache.TrieJournal = stack.ResolvePath(journal) + } if cache.TrieDirtyDisabled && !cache.Preimages { cache.Preimages = true log.Info("Enabling recording of key preimages since archive mode is used") diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index dc41a75d1191..72da99819aa1 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -63,6 +63,7 @@ var ( CacheTrieJournalFlag = &cli.StringFlag{ Name: "cache.trie.journal", Usage: "Disk journal directory for trie cache to survive node restarts", + Value: "trie-disklayer.rlp", Category: flags.DeprecatedCategory, } CacheTrieRejournalFlag = &cli.DurationFlag{ diff --git a/core/blockchain.go b/core/blockchain.go index b347c01251ab..f71541bdb896 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -166,6 +166,7 @@ type CacheConfig struct { Preimages bool // Whether to store preimage of trie key to the disk StateHistory uint64 // Number of blocks from head whose state histories are reserved. StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top + TrieJournal string // Path used to store the trie pathdb journal SnapshotNoBuild bool // Whether the background generation is allowed SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it @@ -196,6 +197,7 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config { // for flushing both trie data and state data to disk. The config name // should be updated to eliminate the confusion. WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024, + Journal: c.TrieJournal, } } return config diff --git a/eth/backend.go b/eth/backend.go index b6538d7a965d..2aa9c6ef6f33 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -205,6 +205,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { StateHistory: config.StateHistory, StateScheme: scheme, ChainHistoryMode: config.HistoryMode, + TrieJournal: config.TrieJournal, } ) if config.VMTrace != "" { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 217a3a76e60a..87656f18211f 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -126,6 +126,7 @@ type Config struct { TrieTimeout time.Duration SnapshotCache int Preimages bool + TrieJournal string // This is the number of blocks for which logs will be cached in the filter system. FilterLogCacheSize int diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index c20646031596..da8bca3d5e1d 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -120,6 +120,7 @@ type Config struct { WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer ReadOnly bool // Flag whether the database is opened in read only mode SnapshotNoBuild bool // Flag Whether the background generation is allowed + Journal string } // sanitize checks the provided user configurations and changes anything that's diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go index 5dc6da92b85b..09e3d45665eb 100644 --- a/triedb/pathdb/journal.go +++ b/triedb/pathdb/journal.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "os" "time" "github.com/ethereum/go-ethereum/common" @@ -51,11 +52,24 @@ const journalVersion uint64 = 3 // loadJournal tries to parse the layer journal from the disk. func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) { - journal := rawdb.ReadTrieJournal(db.diskdb) - if len(journal) == 0 { - return nil, errMissJournal + var reader io.Reader + if db.config.Journal != "" && common.FileExist(db.config.Journal) { + log.Info("Load pathdb disklayer journal", "path", db.config.Journal) + // If a journal file is specified, read it from there + f, err := os.OpenFile(db.config.Journal, os.O_RDONLY, 0644) + if err != nil { + return nil, fmt.Errorf("failed to read journal file %s: %w", db.config.Journal, err) + } + defer f.Close() + reader = f + } else { + journal := rawdb.ReadTrieJournal(db.diskdb) + if len(journal) == 0 { + return nil, errMissJournal + } + reader = bytes.NewReader(journal) } - r := rlp.NewStream(bytes.NewReader(journal), 0) + r := rlp.NewStream(reader, 0) // Firstly, resolve the first element as the journal version version, err := r.Uint64() @@ -315,8 +329,21 @@ func (db *Database) Journal(root common.Hash) error { if db.readOnly { return errDatabaseReadOnly } + + var journal io.Writer + // Store the journal into the database and return + if db.config.Journal != "" { + file, err := os.OpenFile(db.config.Journal, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return fmt.Errorf("failed to open journal file %s: %w", db.config.Journal, err) + } + defer file.Close() + journal = file + } else { + journal = new(bytes.Buffer) + } + // Firstly write out the metadata of journal - journal := new(bytes.Buffer) if err := rlp.Encode(journal, journalVersion); err != nil { return err } @@ -333,11 +360,20 @@ func (db *Database) Journal(root common.Hash) error { if err := l.journal(journal); err != nil { return err } + // Store the journal into the database and return - rawdb.WriteTrieJournal(db.diskdb, journal.Bytes()) + var size int + if db.config.Journal == "" { + data := journal.(*bytes.Buffer) + size = data.Len() + rawdb.WriteTrieJournal(db.diskdb, data.Bytes()) + } else { + stat, _ := journal.(*os.File).Stat() + size = int(stat.Size()) + } // Set the db in read only mode to reject all following mutations db.readOnly = true - log.Info("Persisted dirty state to disk", "size", common.StorageSize(journal.Len()), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Persisted dirty state to disk", "size", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) return nil }