diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 1b1ccfd46f46..907510a9bf4b 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -138,7 +138,13 @@ func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConf return lendingServ } - blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, consensus, vm.Config{}) + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + Preimages: true, + } + blockchain, _ := core.NewBlockChain(database, cacheConfig, genesis.Config, consensus, vm.Config{}) backend := &SimulatedBackend{ database: database, diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index a3863be5d8bc..120b495e4d21 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -100,6 +100,7 @@ var ( utils.CacheGCFlag, utils.CachePrefetchFlag, //utils.TrieCacheGenFlag, + utils.CachePreimagesFlag, utils.CacheLogSizeFlag, utils.FDLimitFlag, utils.CryptoKZGFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index da607db5ff2d..87d20793653e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -308,6 +308,12 @@ var ( Usage: "Enable heuristic state prefetch during block import", Category: flags.PerfCategory, } + CachePreimagesFlag = &cli.BoolFlag{ + Name: "cache-preimages", + Usage: "Enable recording the SHA3/keccak preimages of trie keys (default: true)", + Value: true, + Category: flags.PerfCategory, + } CacheLogSizeFlag = &cli.IntFlag{ Name: "cache-blocklogs", Aliases: []string{"cache.blocklogs"}, @@ -1513,7 +1519,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.NoPruning = ctx.String(GCModeFlag.Name) == "archive" cfg.NoPrefetch = !ctx.Bool(CachePrefetchFlag.Name) - + // Read the value from the flag no matter if it's set or not. + cfg.Preimages = ctx.Bool(CachePreimagesFlag.Name) + if cfg.NoPruning && !cfg.Preimages { + cfg.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") + } if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 } @@ -1780,6 +1791,11 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (chain *core.B TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache, TrieDirtyDisabled: ctx.String(GCModeFlag.Name) == "archive", TrieTimeLimit: ethconfig.Defaults.TrieTimeout, + Preimages: ctx.Bool(CachePreimagesFlag.Name), + } + if cache.TrieDirtyDisabled && !cache.Preimages { + cache.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { cache.TrieCleanLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 diff --git a/core/blockchain.go b/core/blockchain.go index f74b6c151354..2b66d6a96320 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -135,6 +135,7 @@ type CacheConfig struct { TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk + Preimages bool // Whether to store preimage of trie key to the disk } type ResultProcessBlock struct { @@ -238,11 +239,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc := &BlockChain{ - chainConfig: chainConfig, - cacheConfig: cacheConfig, - db: db, - triegc: prque.New[int64, common.Hash](nil), - stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit), + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triegc: prque.New[int64, common.Hash](nil), + stateCache: state.NewDatabaseWithConfig(db, &trie.Config{ + Cache: cacheConfig.TrieCleanLimit, + Preimages: cacheConfig.Preimages, + }), quit: make(chan struct{}), chainmu: syncx.NewClosableMutex(), bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), diff --git a/core/state/database.go b/core/state/database.go index 04942fc0e200..cd3f8665cf4e 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -102,18 +102,18 @@ type Trie interface { } // NewDatabase creates a backing store for state. The returned database is safe for -// concurrent use and retains a few recent expanded trie nodes in memory. To keep -// more historical state in memory, use the NewDatabaseWithCache constructor. +// concurrent use, but does not retain any recent trie nodes in memory. To keep some +// historical state in memory, use the NewDatabaseWithConfig constructor. func NewDatabase(db ethdb.Database) Database { - return NewDatabaseWithCache(db, 0) + return NewDatabaseWithConfig(db, nil) } -// NewDatabase creates a backing store for state. The returned database is safe for -// concurrent use and retains both a few recent expanded trie nodes in memory, as -// well as a lot of collapsed RLP trie nodes in a large memory cache. -func NewDatabaseWithCache(db ethdb.Database, cache int) Database { +// NewDatabaseWithConfig creates a backing store for state. The returned database +// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a +// large memory cache. +func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { return &cachingDB{ - db: trie.NewDatabaseWithCache(db, cache), + db: trie.NewDatabaseWithConfig(db, config), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 2b1cb519fb35..e79b16046474 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -177,7 +177,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Ensure we have a valid starting state before doing any work origin := start.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) if number := start.NumberU64(); number > 0 { start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) @@ -549,7 +549,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } // Otherwise try to reexec blocks until we find a state or reach our limit origin := block.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) for i := uint64(0); i < reexec; i++ { block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) diff --git a/eth/backend.go b/eth/backend.go index 824e5fd9ddfb..874120dc7af8 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -181,6 +181,7 @@ func New(stack *node.Node, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendin TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, + Preimages: config.Preimages, } ) if eth.chainConfig.XDPoS != nil { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6b1316c8305d..d126c79cecbf 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -118,6 +118,7 @@ type Config struct { TrieCleanCache int TrieDirtyCache int TrieTimeout time.Duration + Preimages bool // This is the number of blocks for which logs will be cached in the filter system. FilterLogCacheSize int diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 1dfa6e7f0937..8cb89e32ee90 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -32,6 +32,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TrieCleanCache int TrieDirtyCache int TrieTimeout time.Duration + Preimages bool FilterLogCacheSize int Etherbase common.Address `toml:",omitempty"` MinerThreads int `toml:",omitempty"` @@ -57,6 +58,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TrieCleanCache = c.TrieCleanCache enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout + enc.Preimages = c.Preimages enc.FilterLogCacheSize = c.FilterLogCacheSize enc.Etherbase = c.Etherbase enc.MinerThreads = c.MinerThreads @@ -86,6 +88,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TrieCleanCache *int TrieDirtyCache *int TrieTimeout *time.Duration + Preimages *bool FilterLogCacheSize *int Etherbase *common.Address `toml:",omitempty"` MinerThreads *int `toml:",omitempty"` @@ -138,6 +141,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TrieTimeout != nil { c.TrieTimeout = *dec.TrieTimeout } + if dec.Preimages != nil { + c.Preimages = *dec.Preimages + } if dec.FilterLogCacheSize != nil { c.FilterLogCacheSize = *dec.FilterLogCacheSize } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 140830c5ea21..0f67b1fd8c35 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -58,7 +58,7 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. - database = state.NewDatabaseWithCache(eth.chainDb, 16) + database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) // If we didn't check the dirty database, do check the clean one, otherwise // we would rewind past a persisted block (specific corner case is chain // tracing from the genesis). diff --git a/trie/database.go b/trie/database.go index 6dbd35257a00..bd09bbac208a 100644 --- a/trie/database.go +++ b/trie/database.go @@ -260,29 +260,38 @@ func expandNode(hash hashNode, n node) node { } } +// Config defines all necessary options for database. +type Config struct { + Cache int // Memory allowance (MB) to use for caching trie nodes in memory + Preimages bool // Flag whether the preimage of trie key is recorded +} + // NewDatabase creates a new trie database to store ephemeral trie content before // its written out to disk or garbage collected. No read Cache is created, so all // data retrievals will hit the underlying disk database. func NewDatabase(diskdb ethdb.KeyValueStore) *Database { - return NewDatabaseWithCache(diskdb, 0) + return NewDatabaseWithConfig(diskdb, nil) } -// NewDatabaseWithCache creates a new trie database to store ephemeral trie content -// before its written out to disk or garbage collected. It also acts as a read Cache +// NewDatabaseWithConfig creates a new trie database to store ephemeral trie content +// before its written out to disk or garbage collected. It also acts as a read cache // for nodes loaded from disk. -func NewDatabaseWithCache(diskdb ethdb.KeyValueStore, cache int) *Database { +func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database { var cleans *fastcache.Cache - if cache > 0 { - cleans = fastcache.New(cache * 1024 * 1024) + if config != nil && config.Cache > 0 { + cleans = fastcache.New(config.Cache * 1024 * 1024) } - return &Database{ + db := &Database{ diskdb: diskdb, cleans: cleans, dirties: map[common.Hash]*cachedNode{{}: { children: make(map[common.Hash]uint16), }}, - preimages: make(map[common.Hash][]byte), } + if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future + db.preimages = make(map[common.Hash][]byte) + } + return db } // DiskDB retrieves the persistent storage backing the trie database. @@ -329,6 +338,11 @@ func (db *Database) insert(hash common.Hash, size int, node node) { // // Note, this method assumes that the database's Lock is held! func (db *Database) InsertPreimage(hash common.Hash, preimage []byte) { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return + } + // Track the preimage if a yet unknown one if _, ok := db.preimages[hash]; ok { return } @@ -415,6 +429,10 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { // Preimage retrieves a cached trie Node pre-image from memory. If it cannot be // found cached, the method queries the persistent database for the content. func (db *Database) Preimage(hash common.Hash) []byte { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return nil + } // Retrieve the Node from Cache if available db.Lock.RLock() preimage := db.preimages[hash] @@ -572,12 +590,16 @@ func (db *Database) Cap(limit common.StorageSize) error { // leave for later to deduplicate writes. flushPreimages := db.preimagesSize > 4*1024*1024 if flushPreimages { - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err + if db.preimages == nil { + log.Error("Attempted to write preimages whilst disabled") + } else { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() } - batch.Reset() } } // Keep committing nodes from the flush-list until we're below allowance @@ -614,7 +636,11 @@ func (db *Database) Cap(limit common.StorageSize) error { defer db.Lock.Unlock() if flushPreimages { - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + if db.preimages == nil { + log.Error("Attempted to reset preimage cache whilst disabled") + } else { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } } for db.oldest != oldest { node := db.dirties[db.oldest] @@ -658,20 +684,21 @@ func (db *Database) Commit(node common.Hash, report bool) error { batch := db.diskdb.NewBatch() // Move all of the accumulated preimages into a write batch - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { + if db.preimages != nil { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + // Since we're going to replay trie Node writes into the clean Cache, flush out + // any batched pre-images before continuing. if err := batch.Write(); err != nil { return err } batch.Reset() } - // Since we're going to replay trie Node writes into the clean Cache, flush out - // any batched pre-images before continuing. - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - // Move the trie itself into the batch, flushing if enough data is accumulated nodes, storage := len(db.dirties), db.dirtiesSize @@ -693,8 +720,9 @@ func (db *Database) Commit(node common.Hash, report bool) error { batch.Reset() // Reset the storage counters and bumpd metrics - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 - + if db.preimages != nil { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } memcacheCommitTimeTimer.Update(time.Since(start)) memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index c44c5f3eabe2..84191632c8be 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -141,12 +141,13 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { - t.trie.Db.Lock.Lock() - for hk, key := range t.secKeyCache { - t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key) + if t.trie.Db.preimages != nil { // Ugly direct check but avoids the below write lock + t.trie.Db.Lock.Lock() + for hk, key := range t.secKeyCache { + t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key) + } + t.trie.Db.Lock.Unlock() } - t.trie.Db.Lock.Unlock() - t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate Node database