mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
triedb/pathdb: enable trienode history (#32621)
It's the part-4 for trienode history. The trienode history persistence has been enabled with this PR by flag `history.trienode <non-negative-number>`
This commit is contained in:
parent
588dd94aad
commit
add1890a57
15 changed files with 308 additions and 125 deletions
|
|
@ -120,6 +120,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
|||
utils.LogNoHistoryFlag,
|
||||
utils.LogExportCheckpointsFlag,
|
||||
utils.StateHistoryFlag,
|
||||
utils.TrienodeHistoryFlag,
|
||||
}, utils.DatabaseFlags, debug.Flags),
|
||||
Before: func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ var (
|
|||
utils.LogNoHistoryFlag,
|
||||
utils.LogExportCheckpointsFlag,
|
||||
utils.StateHistoryFlag,
|
||||
utils.TrienodeHistoryFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.EthRequiredBlocksFlag,
|
||||
utils.LegacyWhitelistFlag, // deprecated
|
||||
|
|
|
|||
|
|
@ -295,6 +295,12 @@ var (
|
|||
Value: ethconfig.Defaults.StateHistory,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
TrienodeHistoryFlag = &cli.Int64Flag{
|
||||
Name: "history.trienode",
|
||||
Usage: "Number of recent blocks to retain trienode history for, only relevant in state.scheme=path (default/negative = disabled, 0 = entire chain)",
|
||||
Value: ethconfig.Defaults.TrienodeHistory,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
TransactionHistoryFlag = &cli.Uint64Flag{
|
||||
Name: "history.transactions",
|
||||
Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)",
|
||||
|
|
@ -1699,6 +1705,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(StateHistoryFlag.Name) {
|
||||
cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(TrienodeHistoryFlag.Name) {
|
||||
cfg.TrienodeHistory = ctx.Int64(TrienodeHistoryFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(StateSchemeFlag.Name) {
|
||||
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
|
||||
}
|
||||
|
|
@ -2308,6 +2317,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
Preimages: ctx.Bool(CachePreimagesFlag.Name),
|
||||
StateScheme: scheme,
|
||||
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
||||
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
||||
|
||||
// Disable transaction indexing/unindexing.
|
||||
TxLookupLimit: -1,
|
||||
|
|
|
|||
|
|
@ -177,6 +177,11 @@ type BlockChainConfig struct {
|
|||
// If set to 0, all state histories across the entire chain will be retained;
|
||||
StateHistory uint64
|
||||
|
||||
// Number of blocks from the chain head for which trienode histories are retained.
|
||||
// If set to 0, all trienode histories across the entire chain will be retained;
|
||||
// If set to -1, no trienode history will be retained;
|
||||
TrienodeHistory int64
|
||||
|
||||
// State snapshot related options
|
||||
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||
|
|
@ -255,6 +260,7 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
|
|||
if cfg.StateScheme == rawdb.PathScheme {
|
||||
config.PathDB = &pathdb.Config{
|
||||
StateHistory: cfg.StateHistory,
|
||||
TrienodeHistory: cfg.TrienodeHistory,
|
||||
EnableStateIndexing: cfg.ArchiveMode,
|
||||
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
|
||||
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
SnapshotLimit: config.SnapshotCache,
|
||||
Preimages: config.Preimages,
|
||||
StateHistory: config.StateHistory,
|
||||
TrienodeHistory: config.TrienodeHistory,
|
||||
StateScheme: scheme,
|
||||
ChainHistoryMode: config.HistoryMode,
|
||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ var Defaults = Config{
|
|||
TransactionHistory: 2350000,
|
||||
LogHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
TrienodeHistory: -1,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
|
|
@ -108,6 +109,7 @@ type Config struct {
|
|||
LogNoHistory bool `toml:",omitempty"` // No log search index is maintained.
|
||||
LogExportCheckpoints string // export log index checkpoints to file
|
||||
StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved.
|
||||
TrienodeHistory int64 `toml:",omitempty"` // Number of blocks from the chain head for which trienode histories are retained
|
||||
|
||||
// State scheme represents the scheme used to store ethereum states and trie
|
||||
// nodes on top. It can be 'hash', 'path', or none which means use the scheme
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
LogNoHistory bool `toml:",omitempty"`
|
||||
LogExportCheckpoints string
|
||||
StateHistory uint64 `toml:",omitempty"`
|
||||
TrienodeHistory int64 `toml:",omitempty"`
|
||||
StateScheme string `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
||||
|
|
@ -81,6 +82,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.LogNoHistory = c.LogNoHistory
|
||||
enc.LogExportCheckpoints = c.LogExportCheckpoints
|
||||
enc.StateHistory = c.StateHistory
|
||||
enc.TrienodeHistory = c.TrienodeHistory
|
||||
enc.StateScheme = c.StateScheme
|
||||
enc.RequiredBlocks = c.RequiredBlocks
|
||||
enc.SlowBlockThreshold = c.SlowBlockThreshold
|
||||
|
|
@ -135,6 +137,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
LogNoHistory *bool `toml:",omitempty"`
|
||||
LogExportCheckpoints *string
|
||||
StateHistory *uint64 `toml:",omitempty"`
|
||||
TrienodeHistory *int64 `toml:",omitempty"`
|
||||
StateScheme *string `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold *time.Duration `toml:",omitempty"`
|
||||
|
|
@ -216,6 +219,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.StateHistory != nil {
|
||||
c.StateHistory = *dec.StateHistory
|
||||
}
|
||||
if dec.TrienodeHistory != nil {
|
||||
c.TrienodeHistory = *dec.TrienodeHistory
|
||||
}
|
||||
if dec.StateScheme != nil {
|
||||
c.StateScheme = *dec.StateScheme
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func (b *buffer) size() uint64 {
|
|||
|
||||
// flush persists the in-memory dirty trie node into the disk if the configured
|
||||
// memory threshold is reached. Note, all data must be written atomically.
|
||||
func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64, postFlush func()) {
|
||||
func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezers []ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64, postFlush func()) {
|
||||
if b.done != nil {
|
||||
panic("duplicated flush operation")
|
||||
}
|
||||
|
|
@ -165,12 +165,10 @@ func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.A
|
|||
//
|
||||
// This step is crucial to guarantee that the corresponding state history remains
|
||||
// available for state rollback.
|
||||
if freezer != nil {
|
||||
if err := freezer.SyncAncient(); err != nil {
|
||||
if err := syncHistory(freezers...); err != nil {
|
||||
b.flushErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
nodes := b.nodes.write(batch, nodesCache)
|
||||
accounts, slots := b.states.write(batch, progress, statesCache)
|
||||
rawdb.WritePersistentStateID(batch, id)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ var (
|
|||
// Defaults contains default settings for Ethereum mainnet.
|
||||
var Defaults = &Config{
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
TrienodeHistory: -1,
|
||||
EnableStateIndexing: false,
|
||||
TrieCleanSize: defaultTrieCleanSize,
|
||||
StateCleanSize: defaultStateCleanSize,
|
||||
|
|
@ -62,6 +63,7 @@ var Defaults = &Config{
|
|||
// ReadOnly is the config in order to open database in read only mode.
|
||||
var ReadOnly = &Config{
|
||||
ReadOnly: true,
|
||||
TrienodeHistory: -1,
|
||||
TrieCleanSize: defaultTrieCleanSize,
|
||||
StateCleanSize: defaultStateCleanSize,
|
||||
}
|
||||
|
|
@ -69,6 +71,7 @@ var ReadOnly = &Config{
|
|||
// Config contains the settings for database.
|
||||
type Config struct {
|
||||
StateHistory uint64 // Number of recent blocks to maintain state history for, 0: full chain
|
||||
TrienodeHistory int64 // Number of recent blocks to maintain trienode history for, 0: full chain, negative: disable
|
||||
EnableStateIndexing bool // Whether to enable state history indexing for external state access
|
||||
TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie data
|
||||
StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
|
||||
|
|
@ -108,6 +111,13 @@ func (c *Config) fields() []interface{} {
|
|||
} else {
|
||||
list = append(list, "state-history", fmt.Sprintf("last %d blocks", c.StateHistory))
|
||||
}
|
||||
if c.TrienodeHistory >= 0 {
|
||||
if c.TrienodeHistory == 0 {
|
||||
list = append(list, "trie-history", "entire chain")
|
||||
} else {
|
||||
list = append(list, "trie-history", fmt.Sprintf("last %d blocks", c.TrienodeHistory))
|
||||
}
|
||||
}
|
||||
if c.EnableStateIndexing {
|
||||
list = append(list, "index-history", true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ type Database struct {
|
|||
stateFreezer ethdb.ResettableAncientStore // Freezer for storing state histories, nil possible in tests
|
||||
stateIndexer *historyIndexer // History indexer historical state data, nil possible
|
||||
|
||||
trienodeFreezer ethdb.ResettableAncientStore // Freezer for storing trienode histories, nil possible in tests
|
||||
trienodeIndexer *historyIndexer // History indexer for historical trienode data
|
||||
|
||||
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
||||
}
|
||||
|
||||
|
|
@ -169,11 +172,14 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
// and in-memory layer journal.
|
||||
db.tree = newLayerTree(db.loadLayers())
|
||||
|
||||
// Repair the state history, which might not be aligned with the state
|
||||
// in the key-value store due to an unclean shutdown.
|
||||
if err := db.repairHistory(); err != nil {
|
||||
log.Crit("Failed to repair state history", "err", err)
|
||||
// Repair the history, which might not be aligned with the persistent
|
||||
// state in the key-value store due to an unclean shutdown.
|
||||
states, trienodes, err := repairHistory(db.diskdb, isVerkle, db.config.ReadOnly, db.tree.bottom().stateID(), db.config.TrienodeHistory >= 0)
|
||||
if err != nil {
|
||||
log.Crit("Failed to repair history", "err", err)
|
||||
}
|
||||
db.stateFreezer, db.trienodeFreezer = states, trienodes
|
||||
|
||||
// Disable database in case node is still in the initial state sync stage.
|
||||
if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
|
||||
if err := db.Disable(); err != nil {
|
||||
|
|
@ -187,11 +193,8 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
if err := db.setStateGenerator(); err != nil {
|
||||
log.Crit("Failed to setup the generator", "err", err)
|
||||
}
|
||||
// TODO (rjl493456442) disable the background indexing in read-only mode
|
||||
if db.stateFreezer != nil && db.config.EnableStateIndexing {
|
||||
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory)
|
||||
log.Info("Enabled state history indexing")
|
||||
}
|
||||
db.setHistoryIndexer()
|
||||
|
||||
fields := config.fields()
|
||||
if db.isVerkle {
|
||||
fields = append(fields, "verkle", true)
|
||||
|
|
@ -200,59 +203,28 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
return db
|
||||
}
|
||||
|
||||
// repairHistory truncates leftover state history objects, which may occur due
|
||||
// to an unclean shutdown or other unexpected reasons.
|
||||
func (db *Database) repairHistory() error {
|
||||
// Open the freezer for state history. This mechanism ensures that
|
||||
// only one database instance can be opened at a time to prevent
|
||||
// accidental mutation.
|
||||
ancient, err := db.diskdb.AncientDatadir()
|
||||
if err != nil {
|
||||
// TODO error out if ancient store is disabled. A tons of unit tests
|
||||
// disable the ancient store thus the error here will immediately fail
|
||||
// all of them. Fix the tests first.
|
||||
return nil
|
||||
// setHistoryIndexer initializes the indexers for both state history and
|
||||
// trienode history if available. Note that this function may be called while
|
||||
// existing indexers are still running, so they must be closed beforehand.
|
||||
func (db *Database) setHistoryIndexer() {
|
||||
// TODO (rjl493456442) disable the background indexing in read-only mode
|
||||
if !db.config.EnableStateIndexing {
|
||||
return
|
||||
}
|
||||
freezer, err := rawdb.NewStateFreezer(ancient, db.isVerkle, db.readOnly)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open state history freezer", "err", err)
|
||||
if db.stateFreezer != nil {
|
||||
if db.stateIndexer != nil {
|
||||
db.stateIndexer.close()
|
||||
}
|
||||
db.stateFreezer = freezer
|
||||
|
||||
// Reset the entire state histories if the trie database is not initialized
|
||||
// yet. This action is necessary because these state histories are not
|
||||
// expected to exist without an initialized trie database.
|
||||
id := db.tree.bottom().stateID()
|
||||
if id == 0 {
|
||||
frozen, err := db.stateFreezer.Ancients()
|
||||
if err != nil {
|
||||
log.Crit("Failed to retrieve head of state history", "err", err)
|
||||
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory)
|
||||
log.Info("Enabled state history indexing")
|
||||
}
|
||||
if frozen != 0 {
|
||||
// Purge all state history indexing data first
|
||||
batch := db.diskdb.NewBatch()
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to purge state history index", "err", err)
|
||||
if db.trienodeFreezer != nil {
|
||||
if db.trienodeIndexer != nil {
|
||||
db.trienodeIndexer.close()
|
||||
}
|
||||
if err := db.stateFreezer.Reset(); err != nil {
|
||||
log.Crit("Failed to reset state histories", "err", err)
|
||||
db.trienodeIndexer = newHistoryIndexer(db.diskdb, db.trienodeFreezer, db.tree.bottom().stateID(), typeTrienodeHistory)
|
||||
log.Info("Enabled trienode history indexing")
|
||||
}
|
||||
log.Info("Truncated extraneous state history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Truncate the extra state histories above in freezer in case it's not
|
||||
// aligned with the disk layer. It might happen after a unclean shutdown.
|
||||
pruned, err := truncateFromHead(db.stateFreezer, typeStateHistory, id)
|
||||
if err != nil {
|
||||
log.Crit("Failed to truncate extra state histories", "err", err)
|
||||
}
|
||||
if pruned != 0 {
|
||||
log.Warn("Truncated extra state histories", "number", pruned)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setStateGenerator loads the state generation progress marker and potentially
|
||||
|
|
@ -333,8 +305,13 @@ func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint6
|
|||
if err := db.modifyAllowed(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(rjl493456442) tracking the origins in the following PRs.
|
||||
if err := db.tree.add(root, parentRoot, block, NewNodeSetWithOrigin(nodes.Nodes(), nil), states); err != nil {
|
||||
var nodesWithOrigins *nodeSetWithOrigin
|
||||
if db.config.TrienodeHistory >= 0 {
|
||||
nodesWithOrigins = NewNodeSetWithOrigin(nodes.NodeAndOrigins())
|
||||
} else {
|
||||
nodesWithOrigins = NewNodeSetWithOrigin(nodes.Nodes(), nil)
|
||||
}
|
||||
if err := db.tree.add(root, parentRoot, block, nodesWithOrigins, states); err != nil {
|
||||
return err
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
|
|
@ -422,18 +399,9 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
// all root->id mappings should be removed as well. Since
|
||||
// mappings can be huge and might take a while to clear
|
||||
// them, just leave them in disk and wait for overwriting.
|
||||
if db.stateFreezer != nil {
|
||||
// Purge all state history indexing data first
|
||||
batch.Reset()
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.stateFreezer.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
purgeHistory(db.stateFreezer, db.diskdb, typeStateHistory)
|
||||
purgeHistory(db.trienodeFreezer, db.diskdb, typeTrienodeHistory)
|
||||
|
||||
// Re-enable the database as the final step.
|
||||
db.waitSync = false
|
||||
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
|
||||
|
|
@ -446,11 +414,8 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
// To ensure the history indexer always matches the current state, we must:
|
||||
// 1. Close any existing indexer
|
||||
// 2. Re-initialize the indexer so it starts indexing from the new state root.
|
||||
if db.stateIndexer != nil && db.stateFreezer != nil && db.config.EnableStateIndexing {
|
||||
db.stateIndexer.close()
|
||||
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory)
|
||||
log.Info("Re-enabled state history indexing")
|
||||
}
|
||||
db.setHistoryIndexer()
|
||||
|
||||
log.Info("Rebuilt trie database", "root", root)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -506,6 +471,12 @@ func (db *Database) Recover(root common.Hash) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if db.trienodeFreezer != nil {
|
||||
_, err = truncateFromHead(db.trienodeFreezer, typeTrienodeHistory, dl.stateID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debug("Recovered state", "root", root, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -566,11 +537,21 @@ func (db *Database) Close() error {
|
|||
if db.stateIndexer != nil {
|
||||
db.stateIndexer.close()
|
||||
}
|
||||
// Close the attached state history freezer.
|
||||
if db.stateFreezer == nil {
|
||||
return nil
|
||||
if db.trienodeIndexer != nil {
|
||||
db.trienodeIndexer.close()
|
||||
}
|
||||
return db.stateFreezer.Close()
|
||||
// Close the attached state history freezer.
|
||||
if db.stateFreezer != nil {
|
||||
if err := db.stateFreezer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if db.trienodeFreezer != nil {
|
||||
if err := db.trienodeFreezer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the current storage size of the memory cache in front of the
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
|
|
@ -323,36 +324,60 @@ func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes *no
|
|||
return newDiffLayer(dl, root, id, block, nodes, states)
|
||||
}
|
||||
|
||||
// writeStateHistory stores the state history and indexes if indexing is
|
||||
// writeHistory stores the specified history and indexes if indexing is
|
||||
// permitted.
|
||||
//
|
||||
// What's more, this function also returns a flag indicating whether the
|
||||
// buffer flushing is required, ensuring the persistent state ID is always
|
||||
// greater than or equal to the first history ID.
|
||||
func (dl *diskLayer) writeStateHistory(diff *diffLayer) (bool, error) {
|
||||
// Short circuit if state history is not permitted
|
||||
if dl.db.stateFreezer == nil {
|
||||
func (dl *diskLayer) writeHistory(typ historyType, diff *diffLayer) (bool, error) {
|
||||
var (
|
||||
limit uint64
|
||||
freezer ethdb.AncientStore
|
||||
indexer *historyIndexer
|
||||
writeFunc func(writer ethdb.AncientWriter, dl *diffLayer) error
|
||||
)
|
||||
switch typ {
|
||||
case typeStateHistory:
|
||||
freezer = dl.db.stateFreezer
|
||||
indexer = dl.db.stateIndexer
|
||||
writeFunc = writeStateHistory
|
||||
limit = dl.db.config.StateHistory
|
||||
case typeTrienodeHistory:
|
||||
freezer = dl.db.trienodeFreezer
|
||||
indexer = dl.db.trienodeIndexer
|
||||
writeFunc = writeTrienodeHistory
|
||||
|
||||
// Skip the history commit if the trienode history is not permitted
|
||||
if dl.db.config.TrienodeHistory < 0 {
|
||||
return false, nil
|
||||
}
|
||||
limit = uint64(dl.db.config.TrienodeHistory)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown history type: %v", typ))
|
||||
}
|
||||
// Short circuit if the history freezer is nil
|
||||
if freezer == nil {
|
||||
return false, nil
|
||||
}
|
||||
// Bail out with an error if writing the state history fails.
|
||||
// This can happen, for example, if the device is full.
|
||||
err := writeStateHistory(dl.db.stateFreezer, diff)
|
||||
err := writeFunc(freezer, diff)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Notify the state history indexer for newly created history
|
||||
if dl.db.stateIndexer != nil {
|
||||
if err := dl.db.stateIndexer.extend(diff.stateID()); err != nil {
|
||||
// Notify the history indexer for newly created history
|
||||
if indexer != nil {
|
||||
if err := indexer.extend(diff.stateID()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
// Determine if the persisted history object has exceeded the
|
||||
// configured limitation.
|
||||
limit := dl.db.config.StateHistory
|
||||
if limit == 0 {
|
||||
return false, nil
|
||||
}
|
||||
tail, err := dl.db.stateFreezer.Tail()
|
||||
tail, err := freezer.Tail()
|
||||
if err != nil {
|
||||
return false, err
|
||||
} // firstID = tail+1
|
||||
|
|
@ -375,14 +400,14 @@ func (dl *diskLayer) writeStateHistory(diff *diffLayer) (bool, error) {
|
|||
// These measures ensure the persisted state ID always remains greater
|
||||
// than or equal to the first history ID.
|
||||
if persistentID := rawdb.ReadPersistentStateID(dl.db.diskdb); persistentID < newFirst {
|
||||
log.Debug("Skip tail truncation", "persistentID", persistentID, "tailID", tail+1, "headID", diff.stateID(), "limit", limit)
|
||||
log.Debug("Skip tail truncation", "type", typ, "persistentID", persistentID, "tailID", tail+1, "headID", diff.stateID(), "limit", limit)
|
||||
return true, nil
|
||||
}
|
||||
pruned, err := truncateFromTail(dl.db.stateFreezer, typeStateHistory, newFirst-1)
|
||||
pruned, err := truncateFromTail(freezer, typ, newFirst-1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
log.Debug("Pruned state history", "items", pruned, "tailid", newFirst)
|
||||
log.Debug("Pruned history", "type", typ, "items", pruned, "tailid", newFirst)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
|
@ -396,10 +421,22 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
|||
// Construct and store the state history first. If crash happens after storing
|
||||
// the state history but without flushing the corresponding states(journal),
|
||||
// the stored state history will be truncated from head in the next restart.
|
||||
flush, err := dl.writeStateHistory(bottom)
|
||||
flushA, err := dl.writeHistory(typeStateHistory, bottom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Construct and store the trienode history first. If crash happens after
|
||||
// storing the trienode history but without flushing the corresponding
|
||||
// states(journal), the stored trienode history will be truncated from head
|
||||
// in the next restart.
|
||||
flushB, err := dl.writeHistory(typeTrienodeHistory, bottom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Since the state history and trienode history may be configured with different
|
||||
// lengths, the buffer will be flushed once either of them meets its threshold.
|
||||
flush := flushA || flushB
|
||||
|
||||
// Mark the diskLayer as stale before applying any mutations on top.
|
||||
dl.stale = true
|
||||
|
||||
|
|
@ -448,7 +485,7 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
|||
|
||||
// Freeze the live buffer and schedule background flushing
|
||||
dl.frozen = combined
|
||||
dl.frozen.flush(bottom.root, dl.db.diskdb, dl.db.stateFreezer, progress, dl.nodes, dl.states, bottom.stateID(), func() {
|
||||
dl.frozen.flush(bottom.root, dl.db.diskdb, []ethdb.AncientWriter{dl.db.stateFreezer, dl.db.trienodeFreezer}, progress, dl.nodes, dl.states, bottom.stateID(), func() {
|
||||
// Resume the background generation if it's not completed yet.
|
||||
// The generator is assumed to be available if the progress is
|
||||
// not nil.
|
||||
|
|
@ -504,12 +541,17 @@ func (dl *diskLayer) revert(h *stateHistory) (*diskLayer, error) {
|
|||
|
||||
dl.stale = true
|
||||
|
||||
// Unindex the corresponding state history
|
||||
// Unindex the corresponding history
|
||||
if dl.db.stateIndexer != nil {
|
||||
if err := dl.db.stateIndexer.shorten(dl.id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if dl.db.trienodeIndexer != nil {
|
||||
if err := dl.db.trienodeIndexer.shorten(dl.id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// State change may be applied to node buffer, or the persistent
|
||||
// state, depends on if node buffer is empty or not. If the node
|
||||
// buffer is not empty, it means that the state transition that
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"iter"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
|
@ -323,3 +324,133 @@ func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (
|
|||
// Associated root->id mappings are left in the database.
|
||||
return int(ntail - otail), nil
|
||||
}
|
||||
|
||||
// purgeHistory resets the history and also purges the associated index data.
|
||||
func purgeHistory(store ethdb.ResettableAncientStore, disk ethdb.KeyValueStore, typ historyType) {
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
frozen, err := store.Ancients()
|
||||
if err != nil {
|
||||
log.Crit("Failed to retrieve head of history", "type", typ, "err", err)
|
||||
}
|
||||
if frozen == 0 {
|
||||
return
|
||||
}
|
||||
// Purge all state history indexing data first
|
||||
batch := disk.NewBatch()
|
||||
if typ == typeStateHistory {
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
} else {
|
||||
rawdb.DeleteTrienodeHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteTrienodeHistoryIndexes(batch)
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to purge history index", "type", typ, "err", err)
|
||||
}
|
||||
if err := store.Reset(); err != nil {
|
||||
log.Crit("Failed to reset history", "type", typ, "err", err)
|
||||
}
|
||||
log.Info("Truncated extraneous history", "type", typ)
|
||||
}
|
||||
|
||||
// syncHistory explicitly sync the provided history stores.
|
||||
func syncHistory(stores ...ethdb.AncientWriter) error {
|
||||
for _, store := range stores {
|
||||
if store == nil {
|
||||
continue
|
||||
}
|
||||
if err := store.SyncAncient(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// repairHistory truncates any leftover history objects in either the state
|
||||
// history or the trienode history, which may occur due to an unclean shutdown
|
||||
// or other unexpected events.
|
||||
//
|
||||
// Additionally, this mechanism ensures that the state history and trienode
|
||||
// history remain aligned. Since the trienode history is optional and not
|
||||
// required by regular users, a gap between the trienode history and the
|
||||
// persistent state may appear if the trienode history was disabled during the
|
||||
// previous run. This process detects and resolves such gaps, preventing
|
||||
// unexpected panics.
|
||||
func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint64, enableTrienode bool) (ethdb.ResettableAncientStore, ethdb.ResettableAncientStore, error) {
|
||||
ancient, err := db.AncientDatadir()
|
||||
if err != nil {
|
||||
// TODO error out if ancient store is disabled. A tons of unit tests
|
||||
// disable the ancient store thus the error here will immediately fail
|
||||
// all of them. Fix the tests first.
|
||||
return nil, nil, nil
|
||||
}
|
||||
// State history is mandatory as it is the key component that ensures
|
||||
// resilience to deep reorgs.
|
||||
states, err := rawdb.NewStateFreezer(ancient, isVerkle, readOnly)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open state history freezer", "err", err)
|
||||
}
|
||||
|
||||
// Trienode history is optional and only required for building archive
|
||||
// node with state proofs.
|
||||
var trienodes ethdb.ResettableAncientStore
|
||||
if enableTrienode {
|
||||
trienodes, err = rawdb.NewTrienodeFreezer(ancient, isVerkle, readOnly)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open trienode history freezer", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the both histories if the trie database is not initialized yet.
|
||||
// This action is necessary because these histories are not expected
|
||||
// to exist without an initialized trie database.
|
||||
if stateID == 0 {
|
||||
purgeHistory(states, db, typeStateHistory)
|
||||
purgeHistory(trienodes, db, typeTrienodeHistory)
|
||||
return states, trienodes, nil
|
||||
}
|
||||
// Truncate excessive history entries in either the state history or
|
||||
// the trienode history, ensuring both histories remain aligned with
|
||||
// the state.
|
||||
head, err := states.Ancients()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if stateID > head {
|
||||
return nil, nil, fmt.Errorf("gap between state [#%d] and state history [#%d]", stateID, head)
|
||||
}
|
||||
if trienodes != nil {
|
||||
th, err := trienodes.Ancients()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if stateID > th {
|
||||
return nil, nil, fmt.Errorf("gap between state [#%d] and trienode history [#%d]", stateID, th)
|
||||
}
|
||||
if th != head {
|
||||
log.Info("Histories are not aligned with each other", "state", head, "trienode", th)
|
||||
head = min(head, th)
|
||||
}
|
||||
}
|
||||
head = min(head, stateID)
|
||||
|
||||
// Truncate the extra history elements above in freezer in case it's not
|
||||
// aligned with the state. It might happen after an unclean shutdown.
|
||||
truncate := func(store ethdb.AncientStore, typ historyType, nhead uint64) {
|
||||
if store == nil {
|
||||
return
|
||||
}
|
||||
pruned, err := truncateFromHead(store, typ, nhead)
|
||||
if err != nil {
|
||||
log.Crit("Failed to truncate extra histories", "typ", typ, "err", err)
|
||||
}
|
||||
if pruned != 0 {
|
||||
log.Warn("Truncated extra histories", "typ", typ, "number", pruned)
|
||||
}
|
||||
}
|
||||
truncate(states, typeStateHistory, head)
|
||||
truncate(trienodes, typeTrienodeHistory, head)
|
||||
return states, trienodes, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,7 +672,6 @@ func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, er
|
|||
}
|
||||
|
||||
// writeTrienodeHistory persists the trienode history associated with the given diff layer.
|
||||
// nolint:unused
|
||||
func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
||||
start := time.Now()
|
||||
h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, dl.nodes.nodeOrigin)
|
||||
|
|
|
|||
|
|
@ -338,11 +338,9 @@ func (db *Database) Journal(root common.Hash) error {
|
|||
// but the ancient store is not properly closed, resulting in recent writes
|
||||
// being lost. After a restart, the ancient store would then be misaligned
|
||||
// with the disk layer, causing data corruption.
|
||||
if db.stateFreezer != nil {
|
||||
if err := db.stateFreezer.SyncAncient(); err != nil {
|
||||
if err := syncHistory(db.stateFreezer, db.trienodeFreezer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Store the journal into the database and return
|
||||
var (
|
||||
file *os.File
|
||||
|
|
|
|||
|
|
@ -73,11 +73,8 @@ var (
|
|||
stateHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/data", nil)
|
||||
stateHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/index", nil)
|
||||
|
||||
//nolint:unused
|
||||
trienodeHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/time", nil)
|
||||
//nolint:unused
|
||||
trienodeHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/data", nil)
|
||||
//nolint:unused
|
||||
trienodeHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/index", nil)
|
||||
|
||||
stateIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/index/time", nil)
|
||||
|
|
|
|||
Loading…
Reference in a new issue