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
} }
@ -174,6 +177,8 @@ var defaultCacheConfig = &CacheConfig{
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
@ -445,6 +450,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
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"
@ -154,6 +155,8 @@ type Config struct {
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
@ -171,6 +174,7 @@ type Tree struct {
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
@ -199,7 +203,14 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, roo
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

@ -199,6 +199,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
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

@ -59,6 +59,8 @@ var Defaults = Config{
TrieDirtyCache: 256, TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
SnapshotCache: 102, SnapshotCache: 102,
EnableDiskRootInterval: false,
DiskRootThreshold: 60 * time.Minute,
FilterLogCacheSize: 32, FilterLogCacheSize: 32,
Miner: miner.DefaultConfig, Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig, TxPool: legacypool.DefaultConfig,
@ -124,6 +126,8 @@ type Config struct {
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
} }