update disk root more frequently

This commit is contained in:
Brindrajsinh-Chauhan 2024-04-17 14:27:47 -04:00
parent 3e896c875a
commit 9ff02140a8
7 changed files with 203 additions and 58 deletions

View file

@ -140,6 +140,9 @@ type CacheConfig struct {
StateHistory uint64 // Number of blocks from head whose state histories are reserved. 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 StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
EnableDiskRootInterval bool // Enable update disk root when time threshold is reached. Disabled by default
DiskRootThreshold time.Duration // Time threshold after which to flush the layers to disk
SnapshotNoBuild bool // Whether the background generation is allowed 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 SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
} }
@ -168,12 +171,14 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
// defaultCacheConfig are the default caching values if none are specified by the // defaultCacheConfig are the default caching values if none are specified by the
// user (also used during testing). // user (also used during testing).
var defaultCacheConfig = &CacheConfig{ var defaultCacheConfig = &CacheConfig{
TrieCleanLimit: 256, TrieCleanLimit: 256,
TrieDirtyLimit: 256, TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute, TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 256, SnapshotLimit: 256,
SnapshotWait: true, SnapshotWait: true,
StateScheme: rawdb.HashScheme, StateScheme: rawdb.HashScheme,
EnableDiskRootInterval: false,
DiskRootThreshold: 60 * time.Minute,
} }
// DefaultCacheConfigWithScheme returns a deep copied default cache config with // DefaultCacheConfigWithScheme returns a deep copied default cache config with
@ -441,10 +446,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
recover = true recover = true
} }
snapconfig := snapshot.Config{ snapconfig := snapshot.Config{
CacheSize: bc.cacheConfig.SnapshotLimit, CacheSize: bc.cacheConfig.SnapshotLimit,
Recovery: recover, Recovery: recover,
NoBuild: bc.cacheConfig.SnapshotNoBuild, NoBuild: bc.cacheConfig.SnapshotNoBuild,
AsyncBuild: !bc.cacheConfig.SnapshotWait, AsyncBuild: !bc.cacheConfig.SnapshotWait,
EnableDiskRootInterval: bc.cacheConfig.EnableDiskRootInterval,
DiskRootThreshold: bc.cacheConfig.DiskRootThreshold,
} }
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
} }

View file

@ -76,6 +76,9 @@ var (
bloomDestructHasherOffset = 0 bloomDestructHasherOffset = 0
bloomAccountHasherOffset = 0 bloomAccountHasherOffset = 0
bloomStorageHasherOffset = 0 bloomStorageHasherOffset = 0
// Setting a minimum to prevent very low input from user
minTimeThreshold = 1 * time.Minute
) )
func init() { func init() {

View file

@ -22,6 +22,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -150,10 +151,12 @@ type snapshot interface {
// Config includes the configurations for snapshots. // Config includes the configurations for snapshots.
type Config struct { type Config struct {
CacheSize int // Megabytes permitted to use for read caches CacheSize int // Megabytes permitted to use for read caches
Recovery bool // Indicator that the snapshots is in the recovery mode Recovery bool // Indicator that the snapshots is in the recovery mode
NoBuild bool // Indicator that the snapshots generation is disallowed NoBuild bool // Indicator that the snapshots generation is disallowed
AsyncBuild bool // The snapshot generation is allowed to be constructed asynchronously AsyncBuild bool // The snapshot generation is allowed to be constructed asynchronously
EnableDiskRootInterval bool // The disk root is allowed to update after a time threshold
DiskRootThreshold time.Duration // The threshold to update disk root
} }
// Tree is an Ethereum state snapshot tree. It consists of one persistent base // Tree is an Ethereum state snapshot tree. It consists of one persistent base
@ -166,11 +169,12 @@ type Config struct {
// storage data to avoid expensive multi-level trie lookups; and to allow sorted, // storage data to avoid expensive multi-level trie lookups; and to allow sorted,
// cheap iteration of the account/storage tries for sync aid. // cheap iteration of the account/storage tries for sync aid.
type Tree struct { type Tree struct {
config Config // Snapshots configurations config Config // Snapshots configurations
diskdb ethdb.KeyValueStore // Persistent database to store the snapshot diskdb ethdb.KeyValueStore // Persistent database to store the snapshot
triedb *triedb.Database // In-memory cache to access the trie through triedb *triedb.Database // In-memory cache to access the trie through
layers map[common.Hash]snapshot // Collection of all known layers layers map[common.Hash]snapshot // Collection of all known layers
lock sync.RWMutex lock sync.RWMutex
baseTime time.Time // Reference to calculate the time threshold
// Test hooks // Test hooks
onFlatten func() // Hook invoked when the bottom most diff layers are flattened onFlatten func() // Hook invoked when the bottom most diff layers are flattened
@ -195,11 +199,18 @@ type Tree struct {
func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash) (*Tree, error) { func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash) (*Tree, error) {
// Create a new, empty snapshot tree // Create a new, empty snapshot tree
snap := &Tree{ snap := &Tree{
config: config, config: config,
diskdb: diskdb, diskdb: diskdb,
triedb: triedb, triedb: triedb,
layers: make(map[common.Hash]snapshot), layers: make(map[common.Hash]snapshot),
baseTime: time.Now(),
} }
// If user provided threshold smaller than minimum, set to minimum
if config.DiskRootThreshold < minTimeThreshold {
config.DiskRootThreshold = minTimeThreshold
}
// Attempt to load a previously persisted snapshot and rebuild one if failed // Attempt to load a previously persisted snapshot and rebuild one if failed
head, disabled, err := loadSnapshot(diskdb, triedb, root, config.CacheSize, config.Recovery, config.NoBuild) head, disabled, err := loadSnapshot(diskdb, triedb, root, config.CacheSize, config.Recovery, config.NoBuild)
if disabled { if disabled {
@ -500,7 +511,7 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer {
t.onFlatten() t.onFlatten()
} }
diff.parent = flattened diff.parent = flattened
if flattened.memory < aggregatorMemoryLimit { if (flattened.memory < aggregatorMemoryLimit) && !t.isPastThreshold() {
// Accumulator layer is smaller than the limit, so we can abort, unless // Accumulator layer is smaller than the limit, so we can abort, unless
// there's a snapshot being generated currently. In that case, the trie // there's a snapshot being generated currently. In that case, the trie
// will move from underneath the generator so we **must** merge all the // will move from underneath the generator so we **must** merge all the
@ -512,13 +523,16 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer {
default: default:
panic(fmt.Sprintf("unknown data layer: %T", parent)) panic(fmt.Sprintf("unknown data layer: %T", parent))
} }
// If the bottom-most layer is larger than our memory cap, persist to disk // If the bottom-most layer is larger than our memory cap or time elapsed greater than threshold, persist to disk
bottom := diff.parent.(*diffLayer) bottom := diff.parent.(*diffLayer)
bottom.lock.RLock() bottom.lock.RLock()
base := diffToDisk(bottom) base := diffToDisk(bottom)
bottom.lock.RUnlock() bottom.lock.RUnlock()
// Reset the time reference for next update
t.baseTime = time.Now()
t.layers[base.root] = base t.layers[base.root] = base
diff.parent = base diff.parent = base
return base return base
@ -885,3 +899,9 @@ func (t *Tree) Size() (diffs common.StorageSize, buf common.StorageSize) {
} }
return size, 0 return size, 0
} }
// Check if time threshold is enabled and if the time elapsed is more than the threshold.
// if false, we can abort else we proceed to update the disk root
func (t *Tree) isPastThreshold() bool {
return (t.config.EnableDiskRootInterval && (time.Since(t.baseTime) > t.config.DiskRootThreshold))
}

View file

@ -330,6 +330,103 @@ func TestPostCapBasicDataAccess(t *testing.T) {
} }
} }
func TestForceSnapRootCaps(t *testing.T) {
// setAccount is a helper to construct a random account entry and assign it to
// an account slot in a snapshot
setAccount := func(accKey string) map[common.Hash][]byte {
return map[common.Hash][]byte{
common.HexToHash(accKey): randomAccount(),
}
}
makeRoot := func(height uint64) common.Hash {
var buffer [8]byte
binary.BigEndian.PutUint64(buffer[:], height)
return common.BytesToHash(buffer[:])
}
var (
last = common.HexToHash("0x01")
head common.Hash
)
// Create a starting base layer and a snapshot tree out of it
base := &diskLayer{
diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500),
}
snaps := &Tree{
layers: map[common.Hash]snapshot{
base.root: base,
},
baseTime: time.Now(),
config: Config{
EnableDiskRootInterval: true,
DiskRootThreshold: 30 * time.Minute,
},
}
// adding layers to the tree more than 128
for i := 0; i < 150; i++ {
head = makeRoot(uint64(i + 2))
snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil)
last = head
}
// Currently the tree should have all the 150 layers + disk layer
if count := len(snaps.layers); count != 151 {
t.Errorf("Unexpected number of layers - count %d, expected 151", count)
}
// Now capping without time threshold reached
if err := snaps.Cap(head, 128); err != nil {
t.Error("Error while capping layers", err)
}
// the diskRoot should not have been updated yet since
// the time and the memory limit have not been reached
if firstDiskRoot := snaps.diskRoot(); firstDiskRoot != base.root {
t.Errorf("Disk root should not have updated at this point - actual: %s, expected: %s", firstDiskRoot, base.root)
}
// layers beyond 128 should be flattened into one so total layers
// 128 top layers + 1 flattened layer + disk layer
if newLayers := len(snaps.layers); newLayers != 130 {
t.Errorf("Unexpected number of layers after flatten - count: %d, expected: 130", newLayers)
}
// Setting baseTime 100 min behind to trigger disk update
snaps.baseTime = time.Now().Add(time.Duration(-100) * time.Minute)
// Check if forceSnapshot disabled, time threshold should not trigger snapshot
snaps.config.EnableDiskRootInterval = false
if err := snaps.Cap(head, 128); err != nil {
t.Error("Error while capping layers", err)
}
// the diskRoot should not have been updated yet since
// the force snapshot is disabled
if firstDiskRoot := snaps.diskRoot(); firstDiskRoot != base.root {
t.Errorf("Disk root should not have updated at this point - actual: %s, expected: %s", firstDiskRoot, base.root)
}
// Re-enable forceSnapshot, time threshold should trigger snapshot
snaps.config.EnableDiskRootInterval = true
if err := snaps.Cap(head, 128); err != nil {
t.Error("Error while capping layers", err)
}
// the diskRoot should have been updated now since the time and the memory limit
// have not been reached
if updatedDiskRoot := snaps.diskRoot(); updatedDiskRoot == base.root {
t.Errorf("Disk root did not update at this point - actual: %s", updatedDiskRoot)
}
// disk layer should be updated now to be the flattened layer
// 128 top layers + disk layer
if newLayers := len(snaps.layers); newLayers != 129 {
t.Errorf("Unexpected number of layers after flatten - count: %d, expected: 130", newLayers)
}
}
// TestSnaphots tests the functionality for retrieving the snapshot // TestSnaphots tests the functionality for retrieving the snapshot
// with given head root and the desired depth. // with given head root and the desired depth.
func TestSnaphots(t *testing.T) { func TestSnaphots(t *testing.T) {

View file

@ -190,15 +190,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
} }
cacheConfig = &core.CacheConfig{ cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
TrieCleanNoPrefetch: config.NoPrefetch, TrieCleanNoPrefetch: config.NoPrefetch,
TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyLimit: config.TrieDirtyCache,
TrieDirtyDisabled: config.NoPruning, TrieDirtyDisabled: config.NoPruning,
TrieTimeLimit: config.TrieTimeout, TrieTimeLimit: config.TrieTimeout,
SnapshotLimit: config.SnapshotCache, SnapshotLimit: config.SnapshotCache,
Preimages: config.Preimages, Preimages: config.Preimages,
StateHistory: config.StateHistory, StateHistory: config.StateHistory,
StateScheme: scheme, StateScheme: scheme,
EnableDiskRootInterval: config.EnableDiskRootInterval,
DiskRootThreshold: config.DiskRootThreshold,
} }
) )
if config.VMTrace != "" { if config.VMTrace != "" {

View file

@ -48,25 +48,27 @@ var FullNodeGPO = gasprice.Config{
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{ var Defaults = Config{
SyncMode: downloader.SnapSync, SyncMode: downloader.SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000, TxLookupLimit: 2350000,
TransactionHistory: 2350000, TransactionHistory: 2350000,
StateHistory: params.FullImmutabilityThreshold, StateHistory: params.FullImmutabilityThreshold,
LightPeers: 100, LightPeers: 100,
DatabaseCache: 512, DatabaseCache: 512,
TrieCleanCache: 154, TrieCleanCache: 154,
TrieDirtyCache: 256, TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
SnapshotCache: 102, SnapshotCache: 102,
FilterLogCacheSize: 32, EnableDiskRootInterval: false,
Miner: miner.DefaultConfig, DiskRootThreshold: 60 * time.Minute,
TxPool: legacypool.DefaultConfig, FilterLogCacheSize: 32,
BlobPool: blobpool.DefaultConfig, Miner: miner.DefaultConfig,
RPCGasCap: 50000000, TxPool: legacypool.DefaultConfig,
RPCEVMTimeout: 5 * time.Second, BlobPool: blobpool.DefaultConfig,
GPO: FullNodeGPO, RPCGasCap: 50000000,
RPCTxFeeCap: 1, // 1 ether RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether
} }
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
@ -119,11 +121,13 @@ type Config struct {
DatabaseCache int DatabaseCache int
DatabaseFreezer string DatabaseFreezer string
TrieCleanCache int TrieCleanCache int
TrieDirtyCache int TrieDirtyCache int
TrieTimeout time.Duration TrieTimeout time.Duration
SnapshotCache int SnapshotCache int
Preimages bool Preimages bool
EnableDiskRootInterval bool
DiskRootThreshold time.Duration
// This is the number of blocks for which logs will be cached in the filter system. // This is the number of blocks for which logs will be cached in the filter system.
FilterLogCacheSize int FilterLogCacheSize int

View file

@ -44,6 +44,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
TrieTimeout time.Duration TrieTimeout time.Duration
SnapshotCache int SnapshotCache int
Preimages bool Preimages bool
EnableDiskRootInterval bool
DiskRootThreshold time.Duration
FilterLogCacheSize int FilterLogCacheSize int
Miner miner.Config Miner miner.Config
TxPool legacypool.Config TxPool legacypool.Config
@ -87,6 +89,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.TrieTimeout = c.TrieTimeout enc.TrieTimeout = c.TrieTimeout
enc.SnapshotCache = c.SnapshotCache enc.SnapshotCache = c.SnapshotCache
enc.Preimages = c.Preimages enc.Preimages = c.Preimages
enc.EnableDiskRootInterval = c.EnableDiskRootInterval
enc.DiskRootThreshold = c.DiskRootThreshold
enc.FilterLogCacheSize = c.FilterLogCacheSize enc.FilterLogCacheSize = c.FilterLogCacheSize
enc.Miner = c.Miner enc.Miner = c.Miner
enc.TxPool = c.TxPool enc.TxPool = c.TxPool
@ -134,6 +138,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
TrieTimeout *time.Duration TrieTimeout *time.Duration
SnapshotCache *int SnapshotCache *int
Preimages *bool Preimages *bool
EnableDiskRootInterval *bool
DiskRootThreshold *time.Duration
FilterLogCacheSize *int FilterLogCacheSize *int
Miner *miner.Config Miner *miner.Config
TxPool *legacypool.Config TxPool *legacypool.Config
@ -234,6 +240,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.Preimages != nil { if dec.Preimages != nil {
c.Preimages = *dec.Preimages c.Preimages = *dec.Preimages
} }
if dec.EnableDiskRootInterval != nil {
c.EnableDiskRootInterval = *dec.EnableDiskRootInterval
}
if dec.DiskRootThreshold != nil {
c.DiskRootThreshold = *dec.DiskRootThreshold
}
if dec.FilterLogCacheSize != nil { if dec.FilterLogCacheSize != nil {
c.FilterLogCacheSize = *dec.FilterLogCacheSize c.FilterLogCacheSize = *dec.FilterLogCacheSize
} }