Skip to content

Commit 613af7c

Browse files
authored
Merge pull request #20152 from karalabe/snapshot-5
Dynamic state snapshots
2 parents 93ffb85 + 074efe6 commit 613af7c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+5690
-154
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func (b *SimulatedBackend) rollback() {
124124
statedb, _ := b.blockchain.State()
125125

126126
b.pendingBlock = blocks[0]
127-
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
127+
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
128128
}
129129

130130
// stateByBlockNumber retrieves a state by a given blocknumber.
@@ -480,7 +480,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
480480
statedb, _ := b.blockchain.State()
481481

482482
b.pendingBlock = blocks[0]
483-
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
483+
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
484484
return nil
485485
}
486486

@@ -593,7 +593,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
593593
statedb, _ := b.blockchain.State()
594594

595595
b.pendingBlock = blocks[0]
596-
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
596+
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil)
597597

598598
return nil
599599
}

cmd/evm/runner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,10 @@ func runCmd(ctx *cli.Context) error {
129129
genesisConfig = gen
130130
db := rawdb.NewMemoryDatabase()
131131
genesis := gen.ToBlock(db)
132-
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
132+
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db), nil)
133133
chainConfig = gen.Config
134134
} else {
135-
statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
135+
statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
136136
genesisConfig = new(core.Genesis)
137137
}
138138
if ctx.GlobalString(SenderFlag.Name) != "" {

cmd/evm/staterunner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func stateTestCmd(ctx *cli.Context) error {
9696
for _, st := range test.Subtests() {
9797
// Run the test and aggregate the result
9898
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
99-
state, err := test.Run(st, cfg)
99+
state, err := test.Run(st, cfg, false)
100100
// print state root for evmlab tracing
101101
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
102102
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))

cmd/geth/chaincmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ The dumpgenesis command dumps the genesis block configuration in JSON format to
7979
utils.CacheFlag,
8080
utils.SyncModeFlag,
8181
utils.GCModeFlag,
82+
utils.SnapshotFlag,
8283
utils.CacheDatabaseFlag,
8384
utils.CacheGCFlag,
8485
},
@@ -544,7 +545,7 @@ func dump(ctx *cli.Context) error {
544545
fmt.Println("{}")
545546
utils.Fatalf("block not found")
546547
} else {
547-
state, err := state.New(block.Root(), state.NewDatabase(chainDb))
548+
state, err := state.New(block.Root(), state.NewDatabase(chainDb), nil)
548549
if err != nil {
549550
utils.Fatalf("could not create new state: %v", err)
550551
}

cmd/geth/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ var (
9191
utils.SyncModeFlag,
9292
utils.ExitWhenSyncedFlag,
9393
utils.GCModeFlag,
94+
utils.SnapshotFlag,
9495
utils.LightServeFlag,
9596
utils.LightLegacyServFlag,
9697
utils.LightIngressFlag,
@@ -106,6 +107,7 @@ var (
106107
utils.CacheDatabaseFlag,
107108
utils.CacheTrieFlag,
108109
utils.CacheGCFlag,
110+
utils.CacheSnapshotFlag,
109111
utils.CacheNoPrefetchFlag,
110112
utils.ListenPortFlag,
111113
utils.MaxPeersFlag,

cmd/geth/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ var AppHelpFlagGroups = []flagGroup{
137137
utils.CacheDatabaseFlag,
138138
utils.CacheTrieFlag,
139139
utils.CacheGCFlag,
140+
utils.CacheSnapshotFlag,
140141
utils.CacheNoPrefetchFlag,
141142
},
142143
},

cmd/utils/flags.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ var (
225225
Usage: `Blockchain garbage collection mode ("full", "archive")`,
226226
Value: "full",
227227
}
228+
SnapshotFlag = cli.BoolFlag{
229+
Name: "snapshot",
230+
Usage: `Enables snapshot-database mode -- experimental work in progress feature`,
231+
}
228232
LightKDFFlag = cli.BoolFlag{
229233
Name: "lightkdf",
230234
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
@@ -383,14 +387,19 @@ var (
383387
}
384388
CacheTrieFlag = cli.IntFlag{
385389
Name: "cache.trie",
386-
Usage: "Percentage of cache memory allowance to use for trie caching (default = 25% full mode, 50% archive mode)",
387-
Value: 25,
390+
Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)",
391+
Value: 15,
388392
}
389393
CacheGCFlag = cli.IntFlag{
390394
Name: "cache.gc",
391395
Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)",
392396
Value: 25,
393397
}
398+
CacheSnapshotFlag = cli.IntFlag{
399+
Name: "cache.snapshot",
400+
Usage: "Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode)",
401+
Value: 10,
402+
}
394403
CacheNoPrefetchFlag = cli.BoolFlag{
395404
Name: "cache.noprefetch",
396405
Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)",
@@ -1463,6 +1472,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
14631472
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
14641473
cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
14651474
}
1475+
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) {
1476+
cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
1477+
}
1478+
if !ctx.GlobalIsSet(SnapshotFlag.Name) {
1479+
cfg.SnapshotCache = 0 // Disabled
1480+
}
14661481
if ctx.GlobalIsSet(DocRootFlag.Name) {
14671482
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
14681483
}
@@ -1724,6 +1739,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
17241739
TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache,
17251740
TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
17261741
TrieTimeLimit: eth.DefaultConfig.TrieTimeout,
1742+
SnapshotLimit: eth.DefaultConfig.SnapshotCache,
1743+
}
1744+
if !ctx.GlobalIsSet(SnapshotFlag.Name) {
1745+
cache.SnapshotLimit = 0 // Disabled
17271746
}
17281747
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
17291748
cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100

core/blockchain.go

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/ethereum/go-ethereum/consensus"
3535
"github.com/ethereum/go-ethereum/core/rawdb"
3636
"github.com/ethereum/go-ethereum/core/state"
37+
"github.com/ethereum/go-ethereum/core/state/snapshot"
3738
"github.com/ethereum/go-ethereum/core/types"
3839
"github.com/ethereum/go-ethereum/core/vm"
3940
"github.com/ethereum/go-ethereum/ethdb"
@@ -61,6 +62,10 @@ var (
6162
storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil)
6263
storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil)
6364

65+
snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/account/reads", nil)
66+
snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil)
67+
snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil)
68+
6469
blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
6570
blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
6671
blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
@@ -115,6 +120,9 @@ type CacheConfig struct {
115120
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
116121
TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node)
117122
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
123+
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
124+
125+
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
118126
}
119127

120128
// BlockChain represents the canonical chain given a database with a genesis
@@ -136,6 +144,7 @@ type BlockChain struct {
136144
cacheConfig *CacheConfig // Cache configuration for pruning
137145

138146
db ethdb.Database // Low level persistent database to store final content in
147+
snaps *snapshot.Tree // Snapshot tree for fast trie leaf access
139148
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
140149
gcproc time.Duration // Accumulates canonical block processing for trie dumping
141150

@@ -188,6 +197,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
188197
TrieCleanLimit: 256,
189198
TrieDirtyLimit: 256,
190199
TrieTimeLimit: 5 * time.Minute,
200+
SnapshotLimit: 256,
201+
SnapshotWait: true,
191202
}
192203
}
193204
bodyCache, _ := lru.New(bodyCacheLimit)
@@ -293,6 +304,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
293304
}
294305
}
295306
}
307+
// Load any existing snapshot, regenerating it if loading failed
308+
if bc.cacheConfig.SnapshotLimit > 0 {
309+
bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait)
310+
}
296311
// Take ownership of this particular state
297312
go bc.update()
298313
return bc, nil
@@ -339,7 +354,7 @@ func (bc *BlockChain) loadLastState() error {
339354
return bc.Reset()
340355
}
341356
// Make sure the state associated with the block is available
342-
if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
357+
if _, err := state.New(currentBlock.Root(), bc.stateCache, bc.snaps); err != nil {
343358
// Dangling block without a state associated, init from scratch
344359
log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
345360
if err := bc.repair(&currentBlock); err != nil {
@@ -401,7 +416,7 @@ func (bc *BlockChain) SetHead(head uint64) error {
401416
if newHeadBlock == nil {
402417
newHeadBlock = bc.genesisBlock
403418
} else {
404-
if _, err := state.New(newHeadBlock.Root(), bc.stateCache); err != nil {
419+
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
405420
// Rewound state missing, rolled back to before pivot, reset to genesis
406421
newHeadBlock = bc.genesisBlock
407422
}
@@ -486,6 +501,10 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
486501
headBlockGauge.Update(int64(block.NumberU64()))
487502
bc.chainmu.Unlock()
488503

504+
// Destroy any existing state snapshot and regenerate it in the background
505+
if bc.snaps != nil {
506+
bc.snaps.Rebuild(block.Root())
507+
}
489508
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
490509
return nil
491510
}
@@ -524,7 +543,7 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
524543

525544
// StateAt returns a new mutable state based on a particular point in time.
526545
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
527-
return state.New(root, bc.stateCache)
546+
return state.New(root, bc.stateCache, bc.snaps)
528547
}
529548

530549
// StateCache returns the caching database underpinning the blockchain instance.
@@ -576,7 +595,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
576595
func (bc *BlockChain) repair(head **types.Block) error {
577596
for {
578597
// Abort if we've rewound to a head block that does have associated state
579-
if _, err := state.New((*head).Root(), bc.stateCache); err == nil {
598+
if _, err := state.New((*head).Root(), bc.stateCache, bc.snaps); err == nil {
580599
log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash())
581600
return nil
582601
}
@@ -839,6 +858,14 @@ func (bc *BlockChain) Stop() {
839858

840859
bc.wg.Wait()
841860

861+
// Ensure that the entirety of the state snapshot is journalled to disk.
862+
var snapBase common.Hash
863+
if bc.snaps != nil {
864+
var err error
865+
if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil {
866+
log.Error("Failed to journal state snapshot", "err", err)
867+
}
868+
}
842869
// Ensure the state of a recent block is also stored to disk before exiting.
843870
// We're writing three different states to catch different restart scenarios:
844871
// - HEAD: So we don't need to reprocess any blocks in the general case
@@ -857,6 +884,12 @@ func (bc *BlockChain) Stop() {
857884
}
858885
}
859886
}
887+
if snapBase != (common.Hash{}) {
888+
log.Info("Writing snapshot state to disk", "root", snapBase)
889+
if err := triedb.Commit(snapBase, true); err != nil {
890+
log.Error("Failed to commit recent state trie", "err", err)
891+
}
892+
}
860893
for !bc.triegc.Empty() {
861894
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
862895
}
@@ -1647,7 +1680,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
16471680
if parent == nil {
16481681
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
16491682
}
1650-
statedb, err := state.New(parent.Root, bc.stateCache)
1683+
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
16511684
if err != nil {
16521685
return it.index, err
16531686
}
@@ -1656,9 +1689,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
16561689
var followupInterrupt uint32
16571690
if !bc.cacheConfig.TrieCleanNoPrefetch {
16581691
if followup, err := it.peek(); followup != nil && err == nil {
1659-
throwaway, _ := state.New(parent.Root, bc.stateCache)
1692+
throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)
16601693
go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) {
1661-
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, interrupt)
1694+
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt)
16621695

16631696
blockPrefetchExecuteTimer.Update(time.Since(start))
16641697
if atomic.LoadUint32(interrupt) == 1 {
@@ -1676,14 +1709,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
16761709
return it.index, err
16771710
}
16781711
// Update the metrics touched during block processing
1679-
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them
1680-
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them
1681-
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them
1682-
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them
1712+
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them
1713+
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them
1714+
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them
1715+
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them
1716+
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them
1717+
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them
16831718

16841719
triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation
1685-
trieproc := statedb.AccountReads + statedb.AccountUpdates
1686-
trieproc += statedb.StorageReads + statedb.StorageUpdates
1720+
trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates
1721+
trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates
16871722

16881723
blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash)
16891724

@@ -1712,10 +1747,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
17121747
atomic.StoreUint32(&followupInterrupt, 1)
17131748

17141749
// Update the metrics touched during block commit
1715-
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
1716-
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
1750+
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
1751+
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
1752+
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
17171753

1718-
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits)
1754+
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits)
17191755
blockInsertTimer.UpdateSince(start)
17201756

17211757
switch status {

0 commit comments

Comments
 (0)