core/rawdb, core/state/snapshot: runtime snapshot generation

parent f300c0df
...@@ -106,6 +106,7 @@ var ( ...@@ -106,6 +106,7 @@ var (
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheTrieFlag, utils.CacheTrieFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
utils.CacheSnapshotFlag,
utils.CacheNoPrefetchFlag, utils.CacheNoPrefetchFlag,
utils.ListenPortFlag, utils.ListenPortFlag,
utils.MaxPeersFlag, utils.MaxPeersFlag,
......
...@@ -137,6 +137,7 @@ var AppHelpFlagGroups = []flagGroup{ ...@@ -137,6 +137,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheTrieFlag, utils.CacheTrieFlag,
utils.CacheGCFlag, utils.CacheGCFlag,
utils.CacheSnapshotFlag,
utils.CacheNoPrefetchFlag, utils.CacheNoPrefetchFlag,
}, },
}, },
......
...@@ -383,14 +383,19 @@ var ( ...@@ -383,14 +383,19 @@ var (
} }
CacheTrieFlag = cli.IntFlag{ CacheTrieFlag = cli.IntFlag{
Name: "cache.trie", Name: "cache.trie",
Usage: "Percentage of cache memory allowance to use for trie caching (default = 25% full mode, 50% archive mode)", Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)",
Value: 25, Value: 15,
} }
CacheGCFlag = cli.IntFlag{ CacheGCFlag = cli.IntFlag{
Name: "cache.gc", Name: "cache.gc",
Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)", Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)",
Value: 25, Value: 25,
} }
CacheSnapshotFlag = cli.IntFlag{
Name: "cache.snapshot",
Usage: "Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode)",
Value: 10,
}
CacheNoPrefetchFlag = cli.BoolFlag{ CacheNoPrefetchFlag = cli.BoolFlag{
Name: "cache.noprefetch", Name: "cache.noprefetch",
Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)",
...@@ -1463,6 +1468,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { ...@@ -1463,6 +1468,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
} }
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) {
cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
}
if ctx.GlobalIsSet(DocRootFlag.Name) { if ctx.GlobalIsSet(DocRootFlag.Name) {
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
} }
...@@ -1724,6 +1732,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai ...@@ -1724,6 +1732,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache, TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache,
TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
TrieTimeLimit: eth.DefaultConfig.TrieTimeout, TrieTimeLimit: eth.DefaultConfig.TrieTimeout,
SnapshotLimit: eth.DefaultConfig.SnapshotCache,
} }
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100 cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
......
...@@ -62,8 +62,8 @@ var ( ...@@ -62,8 +62,8 @@ var (
storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil)
snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/accountreads", nil) snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/account/reads", nil)
snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storagereads", nil) snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil)
snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil)
blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
...@@ -120,6 +120,7 @@ type CacheConfig struct { ...@@ -120,6 +120,7 @@ type CacheConfig struct {
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk 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) 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 TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
} }
// BlockChain represents the canonical chain given a database with a genesis // BlockChain represents the canonical chain given a database with a genesis
...@@ -194,6 +195,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par ...@@ -194,6 +195,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
TrieCleanLimit: 256, TrieCleanLimit: 256,
TrieDirtyLimit: 256, TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute, TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 256,
} }
} }
bodyCache, _ := lru.New(bodyCacheLimit) bodyCache, _ := lru.New(bodyCacheLimit)
...@@ -300,10 +302,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par ...@@ -300,10 +302,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
} }
} }
// Load any existing snapshot, regenerating it if loading failed // Load any existing snapshot, regenerating it if loading failed
head := bc.CurrentBlock() bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), "snapshot.rlp", bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root())
if bc.snaps, err = snapshot.New(bc.db, "snapshot.rlp", head.Root()); err != nil {
return nil, err
}
// Take ownership of this particular state // Take ownership of this particular state
go bc.update() go bc.update()
return bc, nil return bc, nil
...@@ -497,6 +497,9 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { ...@@ -497,6 +497,9 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
headBlockGauge.Update(int64(block.NumberU64())) headBlockGauge.Update(int64(block.NumberU64()))
bc.chainmu.Unlock() bc.chainmu.Unlock()
// Destroy any existing state snapshot and regenerate it in the background
bc.snaps.Rebuild(block.Root())
log.Info("Committed new head block", "number", block.Number(), "hash", hash) log.Info("Committed new head block", "number", block.Number(), "hash", hash)
return nil return nil
} }
...@@ -851,7 +854,8 @@ func (bc *BlockChain) Stop() { ...@@ -851,7 +854,8 @@ func (bc *BlockChain) Stop() {
bc.wg.Wait() bc.wg.Wait()
// Ensure that the entirety of the state snapshot is journalled to disk. // Ensure that the entirety of the state snapshot is journalled to disk.
if err := bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { snapBase, err := bc.snaps.Journal(bc.CurrentBlock().Root(), "snapshot.rlp")
if err != nil {
log.Error("Failed to journal state snapshot", "err", err) log.Error("Failed to journal state snapshot", "err", err)
} }
// Ensure the state of a recent block is also stored to disk before exiting. // Ensure the state of a recent block is also stored to disk before exiting.
...@@ -872,6 +876,12 @@ func (bc *BlockChain) Stop() { ...@@ -872,6 +876,12 @@ func (bc *BlockChain) Stop() {
} }
} }
} }
if snapBase != (common.Hash{}) {
log.Info("Writing snapshot state to disk", "root", snapBase)
if err := triedb.Commit(snapBase, true); err != nil {
log.Error("Failed to commit recent state trie", "err", err)
}
}
for !bc.triegc.Empty() { for !bc.triegc.Empty() {
triedb.Dereference(bc.triegc.PopItem().(common.Hash)) triedb.Dereference(bc.triegc.PopItem().(common.Hash))
} }
......
...@@ -282,9 +282,9 @@ func InspectDatabase(db ethdb.Database) error { ...@@ -282,9 +282,9 @@ func InspectDatabase(db ethdb.Database) error {
receiptSize += size receiptSize += size
case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength):
txlookupSize += size txlookupSize += size
case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+common.HashLength): case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength):
accountSnapSize += size accountSnapSize += size
case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+2*common.HashLength): case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength):
storageSnapSize += size storageSnapSize += size
case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength):
preimageSize += size preimageSize += size
......
...@@ -53,9 +53,10 @@ var ( ...@@ -53,9 +53,10 @@ var (
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
StateSnapshotPrefix = []byte("s") // StateSnapshotPrefix + account hash [+ storage hash] -> account/storage trie value SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
SnapshotStoragePrefix = []byte("s") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db configPrefix = []byte("ethereum-config-") // config prefix for the db
...@@ -149,19 +150,19 @@ func txLookupKey(hash common.Hash) []byte { ...@@ -149,19 +150,19 @@ func txLookupKey(hash common.Hash) []byte {
return append(txLookupPrefix, hash.Bytes()...) return append(txLookupPrefix, hash.Bytes()...)
} }
// accountSnapshotKey = StateSnapshotPrefix + hash // accountSnapshotKey = SnapshotAccountPrefix + hash
func accountSnapshotKey(hash common.Hash) []byte { func accountSnapshotKey(hash common.Hash) []byte {
return append(StateSnapshotPrefix, hash.Bytes()...) return append(SnapshotAccountPrefix, hash.Bytes()...)
} }
// storageSnapshotKey = StateSnapshotPrefix + account hash + storage hash // storageSnapshotKey = SnapshotStoragePrefix + account hash + storage hash
func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { func storageSnapshotKey(accountHash, storageHash common.Hash) []byte {
return append(append(StateSnapshotPrefix, accountHash.Bytes()...), storageHash.Bytes()...) return append(append(SnapshotStoragePrefix, accountHash.Bytes()...), storageHash.Bytes()...)
} }
// storageSnapshotsKey = StateSnapshotPrefix + account hash + storage hash // storageSnapshotsKey = SnapshotStoragePrefix + account hash + storage hash
func storageSnapshotsKey(accountHash common.Hash) []byte { func storageSnapshotsKey(accountHash common.Hash) []byte {
return append(StateSnapshotPrefix, accountHash.Bytes()...) return append(SnapshotStoragePrefix, accountHash.Bytes()...)
} }
// bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash // bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash
......
This diff is collapsed.
...@@ -24,7 +24,9 @@ import ( ...@@ -24,7 +24,9 @@ import (
"path" "path"
"testing" "testing"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
...@@ -61,7 +63,7 @@ func TestMergeBasics(t *testing.T) { ...@@ -61,7 +63,7 @@ func TestMergeBasics(t *testing.T) {
} }
} }
// Add some (identical) layers on top // Add some (identical) layers on top
parent := newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage)
child := newDiffLayer(parent, common.Hash{}, accounts, storage) child := newDiffLayer(parent, common.Hash{}, accounts, storage)
child = newDiffLayer(child, common.Hash{}, accounts, storage) child = newDiffLayer(child, common.Hash{}, accounts, storage)
child = newDiffLayer(child, common.Hash{}, accounts, storage) child = newDiffLayer(child, common.Hash{}, accounts, storage)
...@@ -122,7 +124,7 @@ func TestMergeDelete(t *testing.T) { ...@@ -122,7 +124,7 @@ func TestMergeDelete(t *testing.T) {
} }
// Add some flip-flopping layers on top // Add some flip-flopping layers on top
parent := newDiffLayer(emptyLayer{}, common.Hash{}, flip(), storage) parent := newDiffLayer(emptyLayer(), common.Hash{}, flip(), storage)
child := parent.Update(common.Hash{}, flop(), storage) child := parent.Update(common.Hash{}, flop(), storage)
child = child.Update(common.Hash{}, flip(), storage) child = child.Update(common.Hash{}, flip(), storage)
child = child.Update(common.Hash{}, flop(), storage) child = child.Update(common.Hash{}, flop(), storage)
...@@ -165,7 +167,7 @@ func TestInsertAndMerge(t *testing.T) { ...@@ -165,7 +167,7 @@ func TestInsertAndMerge(t *testing.T) {
{ {
var accounts = make(map[common.Hash][]byte) var accounts = make(map[common.Hash][]byte)
var storage = make(map[common.Hash]map[common.Hash][]byte) var storage = make(map[common.Hash]map[common.Hash][]byte)
parent = newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) parent = newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage)
} }
{ {
var accounts = make(map[common.Hash][]byte) var accounts = make(map[common.Hash][]byte)
...@@ -186,34 +188,11 @@ func TestInsertAndMerge(t *testing.T) { ...@@ -186,34 +188,11 @@ func TestInsertAndMerge(t *testing.T) {
} }
} }
type emptyLayer struct{} func emptyLayer() *diskLayer {
return &diskLayer{
func (emptyLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { diskdb: memorydb.New(),
panic("implement me") cache: fastcache.New(500 * 1024),
} }
func (emptyLayer) Journal() error {
panic("implement me")
}
func (emptyLayer) Stale() bool {
panic("implement me")
}
func (emptyLayer) Root() common.Hash {
return common.Hash{}
}
func (emptyLayer) Account(hash common.Hash) (*Account, error) {
return nil, nil
}
func (emptyLayer) AccountRLP(hash common.Hash) ([]byte, error) {
return nil, nil
}
func (emptyLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
return nil, nil
} }
// BenchmarkSearch checks how long it takes to find a non-existing key // BenchmarkSearch checks how long it takes to find a non-existing key
...@@ -234,7 +213,7 @@ func BenchmarkSearch(b *testing.B) { ...@@ -234,7 +213,7 @@ func BenchmarkSearch(b *testing.B) {
return newDiffLayer(parent, common.Hash{}, accounts, storage) return newDiffLayer(parent, common.Hash{}, accounts, storage)
} }
var layer snapshot var layer snapshot
layer = emptyLayer{} layer = emptyLayer()
for i := 0; i < 128; i++ { for i := 0; i < 128; i++ {
layer = fill(layer) layer = fill(layer)
} }
...@@ -272,7 +251,7 @@ func BenchmarkSearchSlot(b *testing.B) { ...@@ -272,7 +251,7 @@ func BenchmarkSearchSlot(b *testing.B) {
return newDiffLayer(parent, common.Hash{}, accounts, storage) return newDiffLayer(parent, common.Hash{}, accounts, storage)
} }
var layer snapshot var layer snapshot
layer = emptyLayer{} layer = emptyLayer()
for i := 0; i < 128; i++ { for i := 0; i < 128; i++ {
layer = fill(layer) layer = fill(layer)
} }
...@@ -313,7 +292,7 @@ func BenchmarkFlatten(b *testing.B) { ...@@ -313,7 +292,7 @@ func BenchmarkFlatten(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
b.StopTimer() b.StopTimer()
var layer snapshot var layer snapshot
layer = emptyLayer{} layer = emptyLayer()
for i := 1; i < 128; i++ { for i := 1; i < 128; i++ {
layer = fill(layer) layer = fill(layer)
} }
...@@ -357,17 +336,14 @@ func BenchmarkJournal(b *testing.B) { ...@@ -357,17 +336,14 @@ func BenchmarkJournal(b *testing.B) {
} }
return newDiffLayer(parent, common.Hash{}, accounts, storage) return newDiffLayer(parent, common.Hash{}, accounts, storage)
} }
var layer snapshot layer := snapshot(new(diskLayer))
layer = &diskLayer{
journal: path.Join(os.TempDir(), "difflayer_journal.tmp"),
}
for i := 1; i < 128; i++ { for i := 1; i < 128; i++ {
layer = fill(layer) layer = fill(layer)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
f, _ := layer.(*diffLayer).journal() f, _, _ := layer.Journal(path.Join(os.TempDir(), "difflayer_journal.tmp"))
f.Close() f.Close()
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package snapshot package snapshot
import ( import (
"bytes"
"sync" "sync"
"github.com/VictoriaMetrics/fastcache" "github.com/VictoriaMetrics/fastcache"
...@@ -24,17 +25,21 @@ import ( ...@@ -24,17 +25,21 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
// diskLayer is a low level persistent snapshot built on top of a key-value store. // diskLayer is a low level persistent snapshot built on top of a key-value store.
type diskLayer struct { type diskLayer struct {
journal string // Path of the snapshot journal to use on shutdown diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot
db ethdb.KeyValueStore // Key-value store containing the base snapshot triedb *trie.Database // Trie node cache for reconstuction purposes
cache *fastcache.Cache // Cache to avoid hitting the disk for direct access cache *fastcache.Cache // Cache to avoid hitting the disk for direct access
root common.Hash // Root hash of the base snapshot root common.Hash // Root hash of the base snapshot
stale bool // Signals that the layer became stale (state progressed) stale bool // Signals that the layer became stale (state progressed)
genMarker []byte // Marker for the state that's indexed during initial layer generation
genAbort chan chan *generatorStats // Notification channel to abort generating the snapshot in this layer
lock sync.RWMutex lock sync.RWMutex
} }
...@@ -80,18 +85,26 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { ...@@ -80,18 +85,26 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) {
if dl.stale { if dl.stale {
return nil, ErrSnapshotStale return nil, ErrSnapshotStale
} }
// If the layer is being generated, ensure the requested hash has already been
// covered by the generator.
if dl.genMarker != nil && bytes.Compare(hash[:], dl.genMarker) > 0 {
return nil, ErrNotCoveredYet
}
// If we're in the disk layer, all diff layers missed
snapshotDirtyAccountMissMeter.Mark(1)
// Try to retrieve the account from the memory cache // Try to retrieve the account from the memory cache
if blob := dl.cache.Get(nil, hash[:]); blob != nil { if blob, found := dl.cache.HasGet(nil, hash[:]); found {
snapshotCleanHitMeter.Mark(1) snapshotCleanAccountHitMeter.Mark(1)
snapshotCleanReadMeter.Mark(int64(len(blob))) snapshotCleanAccountReadMeter.Mark(int64(len(blob)))
return blob, nil return blob, nil
} }
// Cache doesn't contain account, pull from disk and cache for later // Cache doesn't contain account, pull from disk and cache for later
blob := rawdb.ReadAccountSnapshot(dl.db, hash) blob := rawdb.ReadAccountSnapshot(dl.diskdb, hash)
dl.cache.Set(hash[:], blob) dl.cache.Set(hash[:], blob)
snapshotCleanMissMeter.Mark(1) snapshotCleanAccountMissMeter.Mark(1)
snapshotCleanWriteMeter.Mark(int64(len(blob))) snapshotCleanAccountWriteMeter.Mark(int64(len(blob)))
return blob, nil return blob, nil
} }
...@@ -109,18 +122,26 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro ...@@ -109,18 +122,26 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
} }
key := append(accountHash[:], storageHash[:]...) key := append(accountHash[:], storageHash[:]...)
// If the layer is being generated, ensure the requested hash has already been
// covered by the generator.
if dl.genMarker != nil && bytes.Compare(key, dl.genMarker) > 0 {
return nil, ErrNotCoveredYet
}
// If we're in the disk layer, all diff layers missed
snapshotDirtyStorageMissMeter.Mark(1)
// Try to retrieve the storage slot from the memory cache // Try to retrieve the storage slot from the memory cache
if blob := dl.cache.Get(nil, key); blob != nil { if blob, found := dl.cache.HasGet(nil, key); found {
snapshotCleanHitMeter.Mark(1) snapshotCleanStorageHitMeter.Mark(1)
snapshotCleanReadMeter.Mark(int64(len(blob))) snapshotCleanStorageReadMeter.Mark(int64(len(blob)))
return blob, nil return blob, nil
} }
// Cache doesn't contain storage slot, pull from disk and cache for later // Cache doesn't contain storage slot, pull from disk and cache for later
blob := rawdb.ReadStorageSnapshot(dl.db, accountHash, storageHash) blob := rawdb.ReadStorageSnapshot(dl.diskdb, accountHash, storageHash)
dl.cache.Set(key, blob) dl.cache.Set(key, blob)
snapshotCleanMissMeter.Mark(1) snapshotCleanStorageMissMeter.Mark(1)
snapshotCleanWriteMeter.Mark(int64(len(blob))) snapshotCleanStorageWriteMeter.Mark(int64(len(blob)))
return blob, nil return blob, nil
} }
...@@ -131,9 +152,3 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro ...@@ -131,9 +152,3 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer {
return newDiffLayer(dl, blockHash, accounts, storage) return newDiffLayer(dl, blockHash, accounts, storage)
} }
// Journal commits an entire diff hierarchy to disk into a single journal file.
func (dl *diskLayer) Journal() error {
// There's no journalling a disk layer
return nil
}
This diff is collapsed.
This diff is collapsed.
...@@ -18,14 +18,32 @@ package snapshot ...@@ -18,14 +18,32 @@ package snapshot
import ( import (
"bufio" "bufio"
"encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"time"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
// journalGenerator is a disk layer entry containing the generator progress marker.
type journalGenerator struct {
Wiping bool // Whether the database was in progress of being wiped
Done bool // Whether the generator finished creating the snapshot
Marker []byte
Accounts uint64
Slots uint64
Storage uint64
}
// journalAccount is an account entry in a diffLayer's disk journal. // journalAccount is an account entry in a diffLayer's disk journal.
type journalAccount struct { type journalAccount struct {
Hash common.Hash Hash common.Hash
...@@ -39,6 +57,76 @@ type journalStorage struct { ...@@ -39,6 +57,76 @@ type journalStorage struct {
Vals [][]byte Vals [][]byte
} }
// loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cache int, root common.Hash) (snapshot, error) {
// Retrieve the block number and hash of the snapshot, failing if no snapshot
// is present in the database (or crashed mid-update).
baseRoot := rawdb.ReadSnapshotRoot(diskdb)
if baseRoot == (common.Hash{}) {
return nil, errors.New("missing or corrupted snapshot")
}
base := &diskLayer{
diskdb: diskdb,
triedb: triedb,
cache: fastcache.New(cache * 1024 * 1024),
root: baseRoot,
}
// Open the journal, it must exist since even for 0 layer it stores whether
// we've already generated the snapshot or are in progress only
file, err := os.Open(journal)
if err != nil {
return nil, err
}
r := rlp.NewStream(file, 0)
// Read the snapshot generation progress for the disk layer
var generator journalGenerator
if err := r.Decode(&generator); err != nil {
return nil, fmt.Errorf("failed to load snapshot progress marker: %v", err)
}
// Load all the snapshot diffs from the journal
snapshot, err := loadDiffLayer(base, r)
if err != nil {
return nil, err
}
// Entire snapshot journal loaded, sanity check the head and return
// Journal doesn't exist, don't worry if it's not supposed to
if head := snapshot.Root(); head != root {
return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
}
// Everything loaded correctly, resume any suspended operations
if !generator.Done {
// If the generator was still wiping, restart one from scratch (fine for
// now as it's rare and the wiper deletes the stuff it touches anyway, so
// restarting won't incur a lot of extra database hops.
var wiper chan struct{}
if generator.Wiping {
log.Info("Resuming previous snapshot wipe")
wiper = wipeSnapshot(diskdb, false)
}
// Whether or not wiping was in progress, load any generator progress too
base.genMarker = generator.Marker
if base.genMarker == nil {
base.genMarker = []byte{}
}
base.genAbort = make(chan chan *generatorStats)
var origin uint64
if len(generator.Marker) >= 8 {
origin = binary.BigEndian.Uint64(generator.Marker)
}
go base.generate(&generatorStats{
wiping: wiper,
origin: origin,
start: time.Now(),
accounts: generator.Accounts,
slots: generator.Slots,
storage: common.StorageSize(generator.Storage),
})
}
return snapshot, nil
}
// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
// diff and verifying that it can be linked to the requested parent. // diff and verifying that it can be linked to the requested parent.
func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
...@@ -74,39 +162,71 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { ...@@ -74,39 +162,71 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r)
} }
// journal is the internal version of Journal that also returns the journal file // Journal is the internal version of Journal that also returns the journal file
// so subsequent layers know where to write to. // so subsequent layers know where to write to.
func (dl *diffLayer) journal() (io.WriteCloser, error) { func (dl *diskLayer) Journal(path string) (io.WriteCloser, common.Hash, error) {
// If we've reached the bottom, open the journal // If the snapshot is currenty being generated, abort it
var writer io.WriteCloser var stats *generatorStats
if parent, ok := dl.parent.(*diskLayer); ok { if dl.genAbort != nil {
file, err := os.Create(parent.journal) abort := make(chan *generatorStats)
if err != nil { dl.genAbort <- abort
return nil, err
if stats = <-abort; stats != nil {
stats.Log("Journalling in-progress snapshot", dl.genMarker)
} }
writer = file
} }
// If we haven't reached the bottom yet, journal the parent first // Ensure the layer didn't get stale
if writer == nil { dl.lock.RLock()
file, err := dl.parent.(*diffLayer).journal() defer dl.lock.RUnlock()
if err != nil {
return nil, err if dl.stale {
} return nil, common.Hash{}, ErrSnapshotStale
writer = file }
// We've reached the bottom, open the journal
file, err := os.Create(path)
if err != nil {
return nil, common.Hash{}, err
}
// Write out the generator marker
entry := journalGenerator{
Done: dl.genMarker == nil,
Marker: dl.genMarker,
}
if stats != nil {
entry.Wiping = (stats.wiping != nil)
entry.Accounts = stats.accounts
entry.Slots = stats.slots
entry.Storage = uint64(stats.storage)
} }
if err := rlp.Encode(file, entry); err != nil {
file.Close()
return nil, common.Hash{}, err
}
return file, dl.root, nil
}
// Journal is the internal version of Journal that also returns the journal file
// so subsequent layers know where to write to.
func (dl *diffLayer) Journal(path string) (io.WriteCloser, common.Hash, error) {
// Journal the parent first
writer, base, err := dl.parent.Journal(path)
if err != nil {
return nil, common.Hash{}, err
}
// Ensure the layer didn't get stale
dl.lock.RLock() dl.lock.RLock()
defer dl.lock.RUnlock() defer dl.lock.RUnlock()
if dl.stale { if dl.stale {
writer.Close() writer.Close()
return nil, ErrSnapshotStale return nil, common.Hash{}, ErrSnapshotStale
} }
// Everything below was journalled, persist this layer too // Everything below was journalled, persist this layer too
buf := bufio.NewWriter(writer) buf := bufio.NewWriter(writer)
if err := rlp.Encode(buf, dl.root); err != nil { if err := rlp.Encode(buf, dl.root); err != nil {
buf.Flush() buf.Flush()
writer.Close() writer.Close()
return nil, err return nil, common.Hash{}, err
} }
accounts := make([]journalAccount, 0, len(dl.accountData)) accounts := make([]journalAccount, 0, len(dl.accountData))
for hash, blob := range dl.accountData { for hash, blob := range dl.accountData {
...@@ -115,7 +235,7 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { ...@@ -115,7 +235,7 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) {
if err := rlp.Encode(buf, accounts); err != nil { if err := rlp.Encode(buf, accounts); err != nil {
buf.Flush() buf.Flush()
writer.Close() writer.Close()
return nil, err return nil, common.Hash{}, err
} }
storage := make([]journalStorage, 0, len(dl.storageData)) storage := make([]journalStorage, 0, len(dl.storageData))
for hash, slots := range dl.storageData { for hash, slots := range dl.storageData {
...@@ -130,8 +250,8 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { ...@@ -130,8 +250,8 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) {
if err := rlp.Encode(buf, storage); err != nil { if err := rlp.Encode(buf, storage); err != nil {
buf.Flush() buf.Flush()
writer.Close() writer.Close()
return nil, err return nil, common.Hash{}, err
} }
buf.Flush() buf.Flush()
return writer, nil return writer, base, nil
} }
This diff is collapsed.
...@@ -31,9 +31,9 @@ import ( ...@@ -31,9 +31,9 @@ import (
func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) {
// Create an empty base layer and a snapshot tree out of it // Create an empty base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
db: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"), root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500), cache: fastcache.New(1024 * 500),
} }
snaps := &Tree{ snaps := &Tree{
layers: map[common.Hash]snapshot{ layers: map[common.Hash]snapshot{
...@@ -54,7 +54,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { ...@@ -54,7 +54,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) {
t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2)
} }
// Commit the diff layer onto the disk and ensure it's persisted // Commit the diff layer onto the disk and ensure it's persisted
if err := snaps.Cap(common.HexToHash("0x02"), 0, 0); err != nil { if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil {
t.Fatalf("failed to merge diff layer onto disk: %v", err) t.Fatalf("failed to merge diff layer onto disk: %v", err)
} }
// Since the base layer was modified, ensure that data retrievald on the external reference fail // Since the base layer was modified, ensure that data retrievald on the external reference fail
...@@ -76,9 +76,9 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { ...@@ -76,9 +76,9 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) {
func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
// Create an empty base layer and a snapshot tree out of it // Create an empty base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
db: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"), root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500), cache: fastcache.New(1024 * 500),
} }
snaps := &Tree{ snaps := &Tree{
layers: map[common.Hash]snapshot{ layers: map[common.Hash]snapshot{
...@@ -102,7 +102,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { ...@@ -102,7 +102,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3)
} }
// Commit the diff layer onto the disk and ensure it's persisted // Commit the diff layer onto the disk and ensure it's persisted
if err := snaps.Cap(common.HexToHash("0x03"), 2, 0); err != nil { defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit)
aggregatorMemoryLimit = 0
if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil {
t.Fatalf("failed to merge diff layer onto disk: %v", err) t.Fatalf("failed to merge diff layer onto disk: %v", err)
} }
// Since the base layer was modified, ensure that data retrievald on the external reference fail // Since the base layer was modified, ensure that data retrievald on the external reference fail
...@@ -124,9 +127,9 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { ...@@ -124,9 +127,9 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) {
// Create an empty base layer and a snapshot tree out of it // Create an empty base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
db: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"), root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500), cache: fastcache.New(1024 * 500),
} }
snaps := &Tree{ snaps := &Tree{
layers: map[common.Hash]snapshot{ layers: map[common.Hash]snapshot{
...@@ -150,7 +153,7 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { ...@@ -150,7 +153,7 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) {
ref := snaps.Snapshot(common.HexToHash("0x02")) ref := snaps.Snapshot(common.HexToHash("0x02"))
// Flatten the diff layer into the bottom accumulator // Flatten the diff layer into the bottom accumulator
if err := snaps.Cap(common.HexToHash("0x03"), 1, 1024*1024); err != nil { if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil {
t.Fatalf("failed to flatten diff layer into accumulator: %v", err) t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
} }
// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
...@@ -172,9 +175,9 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { ...@@ -172,9 +175,9 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) {
func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
// Create an empty base layer and a snapshot tree out of it // Create an empty base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
db: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"), root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500), cache: fastcache.New(1024 * 500),
} }
snaps := &Tree{ snaps := &Tree{
layers: map[common.Hash]snapshot{ layers: map[common.Hash]snapshot{
...@@ -202,14 +205,14 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { ...@@ -202,14 +205,14 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
// Doing a Cap operation with many allowed layers should be a no-op // Doing a Cap operation with many allowed layers should be a no-op
exp := len(snaps.layers) exp := len(snaps.layers)
if err := snaps.Cap(common.HexToHash("0x04"), 2000, 1024*1024); err != nil { if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil {
t.Fatalf("failed to flatten diff layer into accumulator: %v", err) t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
} }
if got := len(snaps.layers); got != exp { if got := len(snaps.layers); got != exp {
t.Errorf("layers modified, got %d exp %d", got, exp) t.Errorf("layers modified, got %d exp %d", got, exp)
} }
// Flatten the diff layer into the bottom accumulator // Flatten the diff layer into the bottom accumulator
if err := snaps.Cap(common.HexToHash("0x04"), 2, 1024*1024); err != nil { if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil {
t.Fatalf("failed to flatten diff layer into accumulator: %v", err) t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
} }
// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
...@@ -236,9 +239,9 @@ func TestPostCapBasicDataAccess(t *testing.T) { ...@@ -236,9 +239,9 @@ func TestPostCapBasicDataAccess(t *testing.T) {
} }
// Create a starting base layer and a snapshot tree out of it // Create a starting base layer and a snapshot tree out of it
base := &diskLayer{ base := &diskLayer{
db: rawdb.NewMemoryDatabase(), diskdb: rawdb.NewMemoryDatabase(),
root: common.HexToHash("0x01"), root: common.HexToHash("0x01"),
cache: fastcache.New(1024 * 500), cache: fastcache.New(1024 * 500),
} }
snaps := &Tree{ snaps := &Tree{
layers: map[common.Hash]snapshot{ layers: map[common.Hash]snapshot{
...@@ -280,11 +283,11 @@ func TestPostCapBasicDataAccess(t *testing.T) { ...@@ -280,11 +283,11 @@ func TestPostCapBasicDataAccess(t *testing.T) {
t.Error(err) t.Error(err)
} }
// Cap to a bad root should fail // Cap to a bad root should fail
if err := snaps.Cap(common.HexToHash("0x1337"), 0, 1024); err == nil { if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil {
t.Errorf("expected error, got none") t.Errorf("expected error, got none")
} }
// Now, merge the a-chain // Now, merge the a-chain
snaps.Cap(common.HexToHash("0xa3"), 0, 1024) snaps.Cap(common.HexToHash("0xa3"), 0)
// At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is
// the parent of b2, b2 should no longer be able to iterate into parent. // the parent of b2, b2 should no longer be able to iterate into parent.
...@@ -308,7 +311,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { ...@@ -308,7 +311,7 @@ func TestPostCapBasicDataAccess(t *testing.T) {
} }
// Now, merge it again, just for fun. It should now error, since a3 // Now, merge it again, just for fun. It should now error, since a3
// is a disk layer // is a disk layer
if err := snaps.Cap(common.HexToHash("0xa3"), 0, 1024); err == nil { if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil {
t.Error("expected error capping the disk layer, got none") t.Error("expected error capping the disk layer, got none")
} }
} }
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package snapshot
import (
"bytes"
"time"
"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"
)
// wipeSnapshot starts a goroutine to iterate over the entire key-value database
// and delete all the data associated with the snapshot (accounts, storage,
// metadata). After all is done, the snapshot range of the database is compacted
// to free up unused data blocks.
func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} {
// Wipe the snapshot root marker synchronously
if full {
rawdb.DeleteSnapshotRoot(db)
}
// Wipe everything else asynchronously
wiper := make(chan struct{}, 1)
go func() {
if err := wipeContent(db); err != nil {
log.Error("Failed to wipe state snapshot", "err", err) // Database close will trigger this
return
}
close(wiper)
}()
return wiper
}
// wipeContent iterates over the entire key-value database and deletes all the
// data associated with the snapshot (accounts, storage), but not the root hash
// as the wiper is meant to run on a background thread but the root needs to be
// removed in sync to avoid data races. After all is done, the snapshot range of
// the database is compacted to free up unused data blocks.
func wipeContent(db ethdb.KeyValueStore) error {
if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, len(rawdb.SnapshotAccountPrefix)+common.HashLength); err != nil {
return err
}
if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength); err != nil {
return err
}
// Compact the snapshot section of the database to get rid of unused space
start := time.Now()
log.Info("Compacting snapshot account area ")
end := common.CopyBytes(rawdb.SnapshotAccountPrefix)
end[len(end)-1]++
if err := db.Compact(rawdb.SnapshotAccountPrefix, end); err != nil {
return err
}
log.Info("Compacting snapshot storage area ")
end = common.CopyBytes(rawdb.SnapshotStoragePrefix)
end[len(end)-1]++
if err := db.Compact(rawdb.SnapshotStoragePrefix, end); err != nil {
return err
}
log.Info("Compacted snapshot area in database", "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
// wipeKeyRange deletes a range of keys from the database starting with prefix
// and having a specific total key length.
func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int) error {
// Batch deletions together to avoid holding an iterator for too long
var (
batch = db.NewBatch()
items int
)
// Iterate over the key-range and delete all of them
start, logged := time.Now(), time.Now()
it := db.NewIteratorWithStart(prefix)
for it.Next() {
// Skip any keys with the correct prefix but wrong lenth (trie nodes)
key := it.Key()
if !bytes.HasPrefix(key, prefix) {
break
}
if len(key) != keylen {
continue
}
// Delete the key and periodically recreate the batch and iterator
batch.Delete(key)
items++
if items%10000 == 0 {
// Batch too large (or iterator too long lived, flush and recreate)
it.Release()
if err := batch.Write(); err != nil {
return err
}
batch.Reset()
it = db.NewIteratorWithStart(key)
if time.Since(logged) > 8*time.Second {
log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
}
}
it.Release()
if err := batch.Write(); err != nil {
return err
}
log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
...@@ -59,17 +59,31 @@ func TestWipe(t *testing.T) { ...@@ -59,17 +59,31 @@ func TestWipe(t *testing.T) {
// Randomize the suffix, dedup and inject it under the snapshot namespace // Randomize the suffix, dedup and inject it under the snapshot namespace
keysuffix := make([]byte, keysize) keysuffix := make([]byte, keysize)
rand.Read(keysuffix) rand.Read(keysuffix)
db.Put(append(rawdb.StateSnapshotPrefix, keysuffix...), randomHash().Bytes())
if rand.Int31n(2) == 0 {
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
} else {
db.Put(append(rawdb.SnapshotStoragePrefix, keysuffix...), randomHash().Bytes())
}
} }
// Sanity check that all the keys are present // Sanity check that all the keys are present
var items int var items int
it := db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) it := db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix)
defer it.Release()
for it.Next() {
key := it.Key()
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
items++
}
}
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
key := it.Key() key := it.Key()
if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
items++ items++
} }
} }
...@@ -80,16 +94,24 @@ func TestWipe(t *testing.T) { ...@@ -80,16 +94,24 @@ func TestWipe(t *testing.T) {
t.Errorf("snapshot block marker mismatch: have %#x, want <not-nil>", hash) t.Errorf("snapshot block marker mismatch: have %#x, want <not-nil>", hash)
} }
// Wipe all snapshot entries from the database // Wipe all snapshot entries from the database
if err := wipeSnapshot(db); err != nil { <-wipeSnapshot(db, true)
t.Fatalf("failed to wipe snapshot: %v", err)
}
// Iterate over the database end ensure no snapshot information remains // Iterate over the database end ensure no snapshot information remains
it = db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) it = db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix)
defer it.Release()
for it.Next() {
key := it.Key()
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
t.Errorf("snapshot entry remained after wipe: %x", key)
}
}
it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix)
defer it.Release() defer it.Release()
for it.Next() { for it.Next() {
key := it.Key() key := it.Key()
if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
t.Errorf("snapshot entry remained after wipe: %x", key) t.Errorf("snapshot entry remained after wipe: %x", key)
} }
} }
......
...@@ -845,8 +845,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { ...@@ -845,8 +845,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil { if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil {
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
} }
if err := s.snaps.Cap(root, 16, 4*1024*1024); err != nil { if err := s.snaps.Cap(root, 128); err != nil {
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 16, "memory", 4*1024*1024, "err", err) log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err)
} }
} }
s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil
......
...@@ -127,7 +127,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -127,7 +127,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice) config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice)
} }
if config.NoPruning && config.TrieDirtyCache > 0 { if config.NoPruning && config.TrieDirtyCache > 0 {
config.TrieCleanCache += config.TrieDirtyCache config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
config.SnapshotCache += config.TrieDirtyCache * 3 / 5
config.TrieDirtyCache = 0 config.TrieDirtyCache = 0
} }
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
...@@ -184,6 +185,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -184,6 +185,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyLimit: config.TrieDirtyCache,
TrieDirtyDisabled: config.NoPruning, TrieDirtyDisabled: config.NoPruning,
TrieTimeLimit: config.TrieTimeout, TrieTimeLimit: config.TrieTimeout,
SnapshotLimit: config.SnapshotCache,
} }
) )
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
...@@ -204,7 +206,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -204,7 +206,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain)
// Permit the downloader to use the trie cache allowance during fast sync // Permit the downloader to use the trie cache allowance during fast sync
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
checkpoint := config.Checkpoint checkpoint := config.Checkpoint
if checkpoint == nil { if checkpoint == nil {
checkpoint = params.TrustedCheckpoints[genesisHash] checkpoint = params.TrustedCheckpoints[genesisHash]
......
...@@ -50,6 +50,7 @@ var DefaultConfig = Config{ ...@@ -50,6 +50,7 @@ var DefaultConfig = Config{
TrieCleanCache: 256, TrieCleanCache: 256,
TrieDirtyCache: 256, TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
SnapshotCache: 256,
Miner: miner.Config{ Miner: miner.Config{
GasFloor: 8000000, GasFloor: 8000000,
GasCeil: 8000000, GasCeil: 8000000,
...@@ -125,6 +126,7 @@ type Config struct { ...@@ -125,6 +126,7 @@ type Config struct {
TrieCleanCache int TrieCleanCache int
TrieDirtyCache int TrieDirtyCache int
TrieTimeout time.Duration TrieTimeout time.Duration
SnapshotCache int
// Mining options // Mining options
Miner miner.Config Miner miner.Config
......
...@@ -29,7 +29,6 @@ import ( ...@@ -29,7 +29,6 @@ import (
type Iterator struct { type Iterator struct {
nodeIt NodeIterator nodeIt NodeIterator
Nodes int // Number of nodes iterated over
Key []byte // Current data key on which the iterator is positioned on Key []byte // Current data key on which the iterator is positioned on
Value []byte // Current data value on which the iterator is positioned on Value []byte // Current data value on which the iterator is positioned on
Err error Err error
...@@ -47,7 +46,6 @@ func NewIterator(it NodeIterator) *Iterator { ...@@ -47,7 +46,6 @@ func NewIterator(it NodeIterator) *Iterator {
// Next moves the iterator forward one key-value entry. // Next moves the iterator forward one key-value entry.
func (it *Iterator) Next() bool { func (it *Iterator) Next() bool {
for it.nodeIt.Next(true) { for it.nodeIt.Next(true) {
it.Nodes++
if it.nodeIt.Leaf() { if it.nodeIt.Leaf() {
it.Key = it.nodeIt.LeafKey() it.Key = it.nodeIt.LeafKey()
it.Value = it.nodeIt.LeafBlob() it.Value = it.nodeIt.LeafBlob()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment