|
|
|
@ -18,7 +18,6 @@ package snapshot |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"encoding/binary" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"math/big" |
|
|
|
@ -27,13 +26,11 @@ import ( |
|
|
|
|
"github.com/VictoriaMetrics/fastcache" |
|
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil" |
|
|
|
|
"github.com/ethereum/go-ethereum/common/math" |
|
|
|
|
"github.com/ethereum/go-ethereum/core/rawdb" |
|
|
|
|
"github.com/ethereum/go-ethereum/crypto" |
|
|
|
|
"github.com/ethereum/go-ethereum/ethdb" |
|
|
|
|
"github.com/ethereum/go-ethereum/ethdb/memorydb" |
|
|
|
|
"github.com/ethereum/go-ethereum/log" |
|
|
|
|
"github.com/ethereum/go-ethereum/metrics" |
|
|
|
|
"github.com/ethereum/go-ethereum/rlp" |
|
|
|
|
"github.com/ethereum/go-ethereum/trie" |
|
|
|
|
) |
|
|
|
@ -47,14 +44,14 @@ var ( |
|
|
|
|
|
|
|
|
|
// accountCheckRange is the upper limit of the number of accounts involved in
|
|
|
|
|
// each range check. This is a value estimated based on experience. If this
|
|
|
|
|
// value is too large, the failure rate of range prove will increase. Otherwise
|
|
|
|
|
// the value is too small, the efficiency of the state recovery will decrease.
|
|
|
|
|
// range is too large, the failure rate of range proof will increase. Otherwise,
|
|
|
|
|
// if the range is too small, the efficiency of the state recovery will decrease.
|
|
|
|
|
accountCheckRange = 128 |
|
|
|
|
|
|
|
|
|
// storageCheckRange is the upper limit of the number of storage slots involved
|
|
|
|
|
// in each range check. This is a value estimated based on experience. If this
|
|
|
|
|
// value is too large, the failure rate of range prove will increase. Otherwise
|
|
|
|
|
// the value is too small, the efficiency of the state recovery will decrease.
|
|
|
|
|
// range is too large, the failure rate of range proof will increase. Otherwise,
|
|
|
|
|
// if the range is too small, the efficiency of the state recovery will decrease.
|
|
|
|
|
storageCheckRange = 1024 |
|
|
|
|
|
|
|
|
|
// errMissingTrie is returned if the target trie is missing while the generation
|
|
|
|
@ -62,85 +59,6 @@ var ( |
|
|
|
|
errMissingTrie = errors.New("missing trie") |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Metrics in generation
|
|
|
|
|
var ( |
|
|
|
|
snapGeneratedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil) |
|
|
|
|
snapRecoveredAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil) |
|
|
|
|
snapWipedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil) |
|
|
|
|
snapMissallAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil) |
|
|
|
|
snapGeneratedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil) |
|
|
|
|
snapRecoveredStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil) |
|
|
|
|
snapWipedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil) |
|
|
|
|
snapMissallStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil) |
|
|
|
|
snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil) |
|
|
|
|
snapFailedRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil) |
|
|
|
|
|
|
|
|
|
// snapAccountProveCounter measures time spent on the account proving
|
|
|
|
|
snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil) |
|
|
|
|
// snapAccountTrieReadCounter measures time spent on the account trie iteration
|
|
|
|
|
snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil) |
|
|
|
|
// snapAccountSnapReadCounter measues time spent on the snapshot account iteration
|
|
|
|
|
snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil) |
|
|
|
|
// snapAccountWriteCounter measures time spent on writing/updating/deleting accounts
|
|
|
|
|
snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil) |
|
|
|
|
// snapStorageProveCounter measures time spent on storage proving
|
|
|
|
|
snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil) |
|
|
|
|
// snapStorageTrieReadCounter measures time spent on the storage trie iteration
|
|
|
|
|
snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil) |
|
|
|
|
// snapStorageSnapReadCounter measures time spent on the snapshot storage iteration
|
|
|
|
|
snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil) |
|
|
|
|
// snapStorageWriteCounter measures time spent on writing/updating/deleting storages
|
|
|
|
|
snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// generatorStats is a collection of statistics gathered by the snapshot generator
|
|
|
|
|
// for logging purposes.
|
|
|
|
|
type generatorStats struct { |
|
|
|
|
origin uint64 // Origin prefix where generation started
|
|
|
|
|
start time.Time // Timestamp when generation started
|
|
|
|
|
accounts uint64 // Number of accounts indexed(generated or recovered)
|
|
|
|
|
slots uint64 // Number of storage slots indexed(generated or recovered)
|
|
|
|
|
storage common.StorageSize // Total account and storage slot size(generation or recovery)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Log creates an contextual log with the given message and the context pulled
|
|
|
|
|
// from the internally maintained statistics.
|
|
|
|
|
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { |
|
|
|
|
var ctx []interface{} |
|
|
|
|
if root != (common.Hash{}) { |
|
|
|
|
ctx = append(ctx, []interface{}{"root", root}...) |
|
|
|
|
} |
|
|
|
|
// Figure out whether we're after or within an account
|
|
|
|
|
switch len(marker) { |
|
|
|
|
case common.HashLength: |
|
|
|
|
ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) |
|
|
|
|
case 2 * common.HashLength: |
|
|
|
|
ctx = append(ctx, []interface{}{ |
|
|
|
|
"in", common.BytesToHash(marker[:common.HashLength]), |
|
|
|
|
"at", common.BytesToHash(marker[common.HashLength:]), |
|
|
|
|
}...) |
|
|
|
|
} |
|
|
|
|
// Add the usual measurements
|
|
|
|
|
ctx = append(ctx, []interface{}{ |
|
|
|
|
"accounts", gs.accounts, |
|
|
|
|
"slots", gs.slots, |
|
|
|
|
"storage", gs.storage, |
|
|
|
|
"elapsed", common.PrettyDuration(time.Since(gs.start)), |
|
|
|
|
}...) |
|
|
|
|
// Calculate the estimated indexing time based on current stats
|
|
|
|
|
if len(marker) > 0 { |
|
|
|
|
if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { |
|
|
|
|
left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) |
|
|
|
|
|
|
|
|
|
speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
|
|
|
|
|
ctx = append(ctx, []interface{}{ |
|
|
|
|
"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), |
|
|
|
|
}...) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
log.Info(msg, ctx...) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// generateSnapshot regenerates a brand new snapshot based on an existing state
|
|
|
|
|
// database and head block asynchronously. The snapshot is returned immediately
|
|
|
|
|
// and generation is continued in the background until done.
|
|
|
|
@ -248,25 +166,35 @@ func (result *proofResult) forEach(callback func(key []byte, val []byte) error) |
|
|
|
|
//
|
|
|
|
|
// The proof result will be returned if the range proving is finished, otherwise
|
|
|
|
|
// the error will be returned to abort the entire procedure.
|
|
|
|
|
func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { |
|
|
|
|
func (dl *diskLayer) proveRange(ctx *generatorContext, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { |
|
|
|
|
var ( |
|
|
|
|
keys [][]byte |
|
|
|
|
vals [][]byte |
|
|
|
|
proof = rawdb.NewMemoryDatabase() |
|
|
|
|
diskMore = false |
|
|
|
|
iter = ctx.iterator(kind) |
|
|
|
|
start = time.Now() |
|
|
|
|
min = append(prefix, origin...) |
|
|
|
|
) |
|
|
|
|
iter := dl.diskdb.NewIterator(prefix, origin) |
|
|
|
|
defer iter.Release() |
|
|
|
|
|
|
|
|
|
var start = time.Now() |
|
|
|
|
for iter.Next() { |
|
|
|
|
// Ensure the iterated item is always equal or larger than the given origin.
|
|
|
|
|
key := iter.Key() |
|
|
|
|
if len(key) != len(prefix)+common.HashLength { |
|
|
|
|
continue |
|
|
|
|
if bytes.Compare(key, min) < 0 { |
|
|
|
|
return nil, errors.New("invalid iteration position") |
|
|
|
|
} |
|
|
|
|
// Ensure the iterated item still fall in the specified prefix. If
|
|
|
|
|
// not which means the items in the specified area are all visited.
|
|
|
|
|
// Move the iterator a step back since we iterate one extra element
|
|
|
|
|
// out.
|
|
|
|
|
if !bytes.Equal(key[:len(prefix)], prefix) { |
|
|
|
|
iter.Hold() |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
// Break if we've reached the max size, and signal that we're not
|
|
|
|
|
// done yet. Move the iterator a step back since we iterate one
|
|
|
|
|
// extra element out.
|
|
|
|
|
if len(keys) == max { |
|
|
|
|
// Break if we've reached the max size, and signal that we're not
|
|
|
|
|
// done yet.
|
|
|
|
|
iter.Hold() |
|
|
|
|
diskMore = true |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
@ -282,7 +210,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix |
|
|
|
|
// generation to heal the invalid data.
|
|
|
|
|
//
|
|
|
|
|
// Here append the original value to ensure that the number of key and
|
|
|
|
|
// value are the same.
|
|
|
|
|
// value are aligned.
|
|
|
|
|
vals = append(vals, common.CopyBytes(iter.Value())) |
|
|
|
|
log.Error("Failed to convert account state data", "err", err) |
|
|
|
|
} else { |
|
|
|
@ -291,13 +219,13 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Update metrics for database iteration and merkle proving
|
|
|
|
|
if kind == "storage" { |
|
|
|
|
if kind == snapStorage { |
|
|
|
|
snapStorageSnapReadCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
} else { |
|
|
|
|
snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
} |
|
|
|
|
defer func(start time.Time) { |
|
|
|
|
if kind == "storage" { |
|
|
|
|
if kind == snapStorage { |
|
|
|
|
snapStorageProveCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
} else { |
|
|
|
|
snapAccountProveCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
@ -322,7 +250,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix |
|
|
|
|
// Snap state is chunked, generate edge proofs for verification.
|
|
|
|
|
tr, err := trie.New(root, dl.triedb) |
|
|
|
|
if err != nil { |
|
|
|
|
stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) |
|
|
|
|
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) |
|
|
|
|
return nil, errMissingTrie |
|
|
|
|
} |
|
|
|
|
// Firstly find out the key of last iterated element.
|
|
|
|
@ -371,19 +299,23 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix |
|
|
|
|
|
|
|
|
|
// onStateCallback is a function that is called by generateRange, when processing a range of
|
|
|
|
|
// accounts or storage slots. For each element, the callback is invoked.
|
|
|
|
|
// If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
|
|
|
|
|
// If 'write' is true, then this element needs to be updated with the 'val'.
|
|
|
|
|
// If 'write' is false, then this element is already correct, and needs no update. However,
|
|
|
|
|
// for accounts, the storage trie of the account needs to be checked.
|
|
|
|
|
//
|
|
|
|
|
// - If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
|
|
|
|
|
// - If 'write' is true, then this element needs to be updated with the 'val'.
|
|
|
|
|
// - If 'write' is false, then this element is already correct, and needs no update.
|
|
|
|
|
// The 'val' is the canonical encoding of the value (not the slim format for accounts)
|
|
|
|
|
//
|
|
|
|
|
// However, for accounts, the storage trie of the account needs to be checked. Also,
|
|
|
|
|
// dangling storages(storage exists but the corresponding account is missing) need to
|
|
|
|
|
// be cleaned up.
|
|
|
|
|
type onStateCallback func(key []byte, val []byte, write bool, delete bool) error |
|
|
|
|
|
|
|
|
|
// generateRange generates the state segment with particular prefix. Generation can
|
|
|
|
|
// either verify the correctness of existing state through range-proof and skip
|
|
|
|
|
// generation, or iterate trie to regenerate state on demand.
|
|
|
|
|
func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { |
|
|
|
|
func (dl *diskLayer) generateRange(ctx *generatorContext, root common.Hash, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { |
|
|
|
|
// Use range prover to check the validity of the flat state in the range
|
|
|
|
|
result, err := dl.proveRange(stats, root, prefix, kind, origin, max, valueConvertFn) |
|
|
|
|
result, err := dl.proveRange(ctx, root, prefix, kind, origin, max, valueConvertFn) |
|
|
|
|
if err != nil { |
|
|
|
|
return false, nil, err |
|
|
|
|
} |
|
|
|
@ -414,18 +346,17 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, |
|
|
|
|
snapFailedRangeProofMeter.Mark(1) |
|
|
|
|
|
|
|
|
|
// Special case, the entire trie is missing. In the original trie scheme,
|
|
|
|
|
// all the duplicated subtries will be filter out(only one copy of data
|
|
|
|
|
// all the duplicated subtries will be filtered out (only one copy of data
|
|
|
|
|
// will be stored). While in the snapshot model, all the storage tries
|
|
|
|
|
// belong to different contracts will be kept even they are duplicated.
|
|
|
|
|
// Track it to a certain extent remove the noise data used for statistics.
|
|
|
|
|
if origin == nil && last == nil { |
|
|
|
|
meter := snapMissallAccountMeter |
|
|
|
|
if kind == "storage" { |
|
|
|
|
if kind == snapStorage { |
|
|
|
|
meter = snapMissallStorageMeter |
|
|
|
|
} |
|
|
|
|
meter.Mark(1) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// We use the snap data to build up a cache which can be used by the
|
|
|
|
|
// main account trie as a primary lookup when resolving hashes
|
|
|
|
|
var snapNodeCache ethdb.KeyValueStore |
|
|
|
@ -439,15 +370,16 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, |
|
|
|
|
root, _, _ := snapTrie.Commit(nil) |
|
|
|
|
snapTrieDb.Commit(root, false, nil) |
|
|
|
|
} |
|
|
|
|
// Construct the trie for state iteration, reuse the trie
|
|
|
|
|
// if it's already opened with some nodes resolved.
|
|
|
|
|
tr := result.tr |
|
|
|
|
if tr == nil { |
|
|
|
|
tr, err = trie.New(root, dl.triedb) |
|
|
|
|
if err != nil { |
|
|
|
|
stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) |
|
|
|
|
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) |
|
|
|
|
return false, nil, errMissingTrie |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
trieMore bool |
|
|
|
|
nodeIt = tr.NodeIterator(origin) |
|
|
|
@ -466,6 +398,7 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, |
|
|
|
|
internal time.Duration |
|
|
|
|
) |
|
|
|
|
nodeIt.AddResolver(snapNodeCache) |
|
|
|
|
|
|
|
|
|
for iter.Next() { |
|
|
|
|
if last != nil && bytes.Compare(iter.Key, last) > 0 { |
|
|
|
|
trieMore = true |
|
|
|
@ -519,7 +452,7 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, |
|
|
|
|
internal += time.Since(istart) |
|
|
|
|
|
|
|
|
|
// Update metrics for counting trie iteration
|
|
|
|
|
if kind == "storage" { |
|
|
|
|
if kind == snapStorage { |
|
|
|
|
snapStorageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) |
|
|
|
|
} else { |
|
|
|
|
snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) |
|
|
|
@ -534,66 +467,69 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, |
|
|
|
|
|
|
|
|
|
// checkAndFlush checks if an interruption signal is received or the
|
|
|
|
|
// batch size has exceeded the allowance.
|
|
|
|
|
func (dl *diskLayer) checkAndFlush(current []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error { |
|
|
|
|
func (dl *diskLayer) checkAndFlush(ctx *generatorContext, current []byte) error { |
|
|
|
|
var abort chan *generatorStats |
|
|
|
|
select { |
|
|
|
|
case abort = <-dl.genAbort: |
|
|
|
|
default: |
|
|
|
|
} |
|
|
|
|
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { |
|
|
|
|
if ctx.batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { |
|
|
|
|
if bytes.Compare(current, dl.genMarker) < 0 { |
|
|
|
|
log.Error("Snapshot generator went backwards", "current", fmt.Sprintf("%x", current), "genMarker", fmt.Sprintf("%x", dl.genMarker)) |
|
|
|
|
} |
|
|
|
|
// Flush out the batch anyway no matter it's empty or not.
|
|
|
|
|
// It's possible that all the states are recovered and the
|
|
|
|
|
// generation indeed makes progress.
|
|
|
|
|
journalProgress(batch, current, stats) |
|
|
|
|
journalProgress(ctx.batch, current, ctx.stats) |
|
|
|
|
|
|
|
|
|
if err := batch.Write(); err != nil { |
|
|
|
|
if err := ctx.batch.Write(); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
batch.Reset() |
|
|
|
|
ctx.batch.Reset() |
|
|
|
|
|
|
|
|
|
dl.lock.Lock() |
|
|
|
|
dl.genMarker = current |
|
|
|
|
dl.lock.Unlock() |
|
|
|
|
|
|
|
|
|
if abort != nil { |
|
|
|
|
stats.Log("Aborting state snapshot generation", dl.root, current) |
|
|
|
|
ctx.stats.Log("Aborting state snapshot generation", dl.root, current) |
|
|
|
|
return newAbortErr(abort) // bubble up an error for interruption
|
|
|
|
|
} |
|
|
|
|
// Don't hold the iterators too long, release them to let compactor works
|
|
|
|
|
ctx.reopenIterator(snapAccount) |
|
|
|
|
ctx.reopenIterator(snapStorage) |
|
|
|
|
} |
|
|
|
|
if time.Since(*logged) > 8*time.Second { |
|
|
|
|
stats.Log("Generating state snapshot", dl.root, current) |
|
|
|
|
*logged = time.Now() |
|
|
|
|
if time.Since(ctx.logged) > 8*time.Second { |
|
|
|
|
ctx.stats.Log("Generating state snapshot", dl.root, current) |
|
|
|
|
ctx.logged = time.Now() |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// generateStorages generates the missing storage slots of the specific contract.
|
|
|
|
|
// It's supposed to restart the generation from the given origin position.
|
|
|
|
|
func generateStorages(dl *diskLayer, account common.Hash, storageRoot common.Hash, storeMarker []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error { |
|
|
|
|
func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash, storageRoot common.Hash, storeMarker []byte) error { |
|
|
|
|
onStorage := func(key []byte, val []byte, write bool, delete bool) error { |
|
|
|
|
defer func(start time.Time) { |
|
|
|
|
snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
}(time.Now()) |
|
|
|
|
|
|
|
|
|
if delete { |
|
|
|
|
rawdb.DeleteStorageSnapshot(batch, account, common.BytesToHash(key)) |
|
|
|
|
rawdb.DeleteStorageSnapshot(ctx.batch, account, common.BytesToHash(key)) |
|
|
|
|
snapWipedStorageMeter.Mark(1) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
if write { |
|
|
|
|
rawdb.WriteStorageSnapshot(batch, account, common.BytesToHash(key), val) |
|
|
|
|
rawdb.WriteStorageSnapshot(ctx.batch, account, common.BytesToHash(key), val) |
|
|
|
|
snapGeneratedStorageMeter.Mark(1) |
|
|
|
|
} else { |
|
|
|
|
snapRecoveredStorageMeter.Mark(1) |
|
|
|
|
} |
|
|
|
|
stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val)) |
|
|
|
|
stats.slots++ |
|
|
|
|
ctx.stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val)) |
|
|
|
|
ctx.stats.slots++ |
|
|
|
|
|
|
|
|
|
// If we've exceeded our batch allowance or termination was requested, flush to disk
|
|
|
|
|
if err := dl.checkAndFlush(append(account[:], key...), batch, stats, logged); err != nil { |
|
|
|
|
if err := dl.checkAndFlush(ctx, append(account[:], key...)); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
@ -601,7 +537,7 @@ func generateStorages(dl *diskLayer, account common.Hash, storageRoot common.Has |
|
|
|
|
// Loop for re-generating the missing storage slots.
|
|
|
|
|
var origin = common.CopyBytes(storeMarker) |
|
|
|
|
for { |
|
|
|
|
exhausted, last, err := dl.generateRange(storageRoot, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), "storage", origin, storageCheckRange, stats, onStorage, nil) |
|
|
|
|
exhausted, last, err := dl.generateRange(ctx, storageRoot, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil) |
|
|
|
|
if err != nil { |
|
|
|
|
return err // The procedure it aborted, either by external signal or internal error.
|
|
|
|
|
} |
|
|
|
@ -619,23 +555,19 @@ func generateStorages(dl *diskLayer, account common.Hash, storageRoot common.Has |
|
|
|
|
// generateAccounts generates the missing snapshot accounts as well as their
|
|
|
|
|
// storage slots in the main trie. It's supposed to restart the generation
|
|
|
|
|
// from the given origin position.
|
|
|
|
|
func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats *generatorStats, logged *time.Time) error { |
|
|
|
|
func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) error { |
|
|
|
|
onAccount := func(key []byte, val []byte, write bool, delete bool) error { |
|
|
|
|
var ( |
|
|
|
|
start = time.Now() |
|
|
|
|
accountHash = common.BytesToHash(key) |
|
|
|
|
) |
|
|
|
|
// Make sure to clear all dangling storages before this account
|
|
|
|
|
account := common.BytesToHash(key) |
|
|
|
|
ctx.removeStorageBefore(account) |
|
|
|
|
|
|
|
|
|
start := time.Now() |
|
|
|
|
if delete { |
|
|
|
|
rawdb.DeleteAccountSnapshot(batch, accountHash) |
|
|
|
|
rawdb.DeleteAccountSnapshot(ctx.batch, account) |
|
|
|
|
snapWipedAccountMeter.Mark(1) |
|
|
|
|
|
|
|
|
|
// Ensure that any previous snapshot storage values are cleared
|
|
|
|
|
prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...) |
|
|
|
|
keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength |
|
|
|
|
if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
|
|
|
|
|
ctx.removeStorageAt(account) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
// Retrieve the current account and flatten it into the internal format
|
|
|
|
@ -649,7 +581,7 @@ func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats |
|
|
|
|
log.Crit("Invalid account encountered during snapshot creation", "err", err) |
|
|
|
|
} |
|
|
|
|
// If the account is not yet in-progress, write it out
|
|
|
|
|
if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { |
|
|
|
|
if accMarker == nil || !bytes.Equal(account[:], accMarker) { |
|
|
|
|
dataLen := len(val) // Approximate size, saves us a round of RLP-encoding
|
|
|
|
|
if !write { |
|
|
|
|
if bytes.Equal(acc.CodeHash, emptyCode[:]) { |
|
|
|
@ -662,44 +594,34 @@ func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats |
|
|
|
|
} else { |
|
|
|
|
data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) |
|
|
|
|
dataLen = len(data) |
|
|
|
|
rawdb.WriteAccountSnapshot(batch, accountHash, data) |
|
|
|
|
rawdb.WriteAccountSnapshot(ctx.batch, account, data) |
|
|
|
|
snapGeneratedAccountMeter.Mark(1) |
|
|
|
|
} |
|
|
|
|
stats.storage += common.StorageSize(1 + common.HashLength + dataLen) |
|
|
|
|
stats.accounts++ |
|
|
|
|
ctx.stats.storage += common.StorageSize(1 + common.HashLength + dataLen) |
|
|
|
|
ctx.stats.accounts++ |
|
|
|
|
} |
|
|
|
|
marker := accountHash[:] |
|
|
|
|
// If the snap generation goes here after interrupted, genMarker may go backward
|
|
|
|
|
// when last genMarker is consisted of accountHash and storageHash
|
|
|
|
|
marker := account[:] |
|
|
|
|
if accMarker != nil && bytes.Equal(marker, accMarker) && len(dl.genMarker) > common.HashLength { |
|
|
|
|
marker = dl.genMarker[:] |
|
|
|
|
} |
|
|
|
|
// If we've exceeded our batch allowance or termination was requested, flush to disk
|
|
|
|
|
if err := dl.checkAndFlush(marker, batch, stats, logged); err != nil { |
|
|
|
|
if err := dl.checkAndFlush(ctx, marker); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) // let's count flush time as well
|
|
|
|
|
|
|
|
|
|
// If the iterated account is the contract, create a further loop to
|
|
|
|
|
// verify or regenerate the contract storage.
|
|
|
|
|
if acc.Root == emptyRoot { |
|
|
|
|
// If the root is empty, we still need to ensure that any previous snapshot
|
|
|
|
|
// storage values are cleared
|
|
|
|
|
// TODO: investigate if this can be avoided, this will be very costly since it
|
|
|
|
|
// affects every single EOA account
|
|
|
|
|
// - Perhaps we can avoid if where codeHash is emptyCode
|
|
|
|
|
prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...) |
|
|
|
|
keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength |
|
|
|
|
if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
ctx.removeStorageAt(account) |
|
|
|
|
} else { |
|
|
|
|
snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) |
|
|
|
|
|
|
|
|
|
var storeMarker []byte |
|
|
|
|
if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { |
|
|
|
|
if accMarker != nil && bytes.Equal(account[:], accMarker) && len(dl.genMarker) > common.HashLength { |
|
|
|
|
storeMarker = dl.genMarker[common.HashLength:] |
|
|
|
|
} |
|
|
|
|
if err := generateStorages(dl, accountHash, acc.Root, storeMarker, batch, stats, logged); err != nil { |
|
|
|
|
if err := generateStorages(ctx, dl, account, acc.Root, storeMarker); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -707,25 +629,26 @@ func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats |
|
|
|
|
accMarker = nil |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
// Always reset the initial account range as 1 whenever recover from the interruption.
|
|
|
|
|
// Always reset the initial account range as 1 whenever recover from the
|
|
|
|
|
// interruption. TODO(rjl493456442) can we remove it?
|
|
|
|
|
var accountRange = accountCheckRange |
|
|
|
|
if len(accMarker) > 0 { |
|
|
|
|
accountRange = 1 |
|
|
|
|
} |
|
|
|
|
// Global loop for re-generating the account snapshots + all layered storage snapshots.
|
|
|
|
|
origin := common.CopyBytes(accMarker) |
|
|
|
|
for { |
|
|
|
|
exhausted, last, err := dl.generateRange(dl.root, rawdb.SnapshotAccountPrefix, "account", origin, accountRange, stats, onAccount, FullAccountRLP) |
|
|
|
|
exhausted, last, err := dl.generateRange(ctx, dl.root, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP) |
|
|
|
|
if err != nil { |
|
|
|
|
return err // The procedure it aborted, either by external signal or internal error.
|
|
|
|
|
} |
|
|
|
|
// Abort the procedure if the entire snapshot is generated
|
|
|
|
|
if exhausted { |
|
|
|
|
origin = increaseKey(last) |
|
|
|
|
|
|
|
|
|
// Last step, cleanup the storages after the last account.
|
|
|
|
|
// All the left storages should be treated as dangling.
|
|
|
|
|
if origin == nil || exhausted { |
|
|
|
|
ctx.removeStorageLeft() |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
if origin = increaseKey(last); origin == nil { |
|
|
|
|
break // special case, the last is 0xffffffff...fff
|
|
|
|
|
} |
|
|
|
|
accountRange = accountCheckRange |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
@ -736,19 +659,27 @@ func generateAccounts(dl *diskLayer, accMarker []byte, batch ethdb.Batch, stats |
|
|
|
|
// gathering and logging, since the method surfs the blocks as they arrive, often
|
|
|
|
|
// being restarted.
|
|
|
|
|
func (dl *diskLayer) generate(stats *generatorStats) { |
|
|
|
|
var accMarker []byte |
|
|
|
|
var ( |
|
|
|
|
accMarker []byte |
|
|
|
|
abort chan *generatorStats |
|
|
|
|
) |
|
|
|
|
if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
|
|
|
|
|
accMarker = dl.genMarker[:common.HashLength] |
|
|
|
|
} |
|
|
|
|
var ( |
|
|
|
|
batch = dl.diskdb.NewBatch() |
|
|
|
|
logged = time.Now() |
|
|
|
|
abort chan *generatorStats |
|
|
|
|
) |
|
|
|
|
stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker) |
|
|
|
|
|
|
|
|
|
// Generate the snapshot accounts from the point where they left off.
|
|
|
|
|
if err := generateAccounts(dl, accMarker, batch, stats, &logged); err != nil { |
|
|
|
|
// Initialize the global generator context. The snapshot iterators are
|
|
|
|
|
// opened at the interrupted position because the assumption is held
|
|
|
|
|
// that all the snapshot data are generated correctly before the marker.
|
|
|
|
|
// Even if the snapshot data is updated during the interruption (before
|
|
|
|
|
// or at the marker), the assumption is still held.
|
|
|
|
|
// For the account or storage slot at the interruption, they will be
|
|
|
|
|
// processed twice by the generator(they are already processed in the
|
|
|
|
|
// last run) but it's fine.
|
|
|
|
|
ctx := newGeneratorContext(stats, dl.diskdb, accMarker, dl.genMarker) |
|
|
|
|
defer ctx.close() |
|
|
|
|
|
|
|
|
|
if err := generateAccounts(ctx, dl, accMarker); err != nil { |
|
|
|
|
// Extract the received interruption signal if exists
|
|
|
|
|
if aerr, ok := err.(*abortErr); ok { |
|
|
|
|
abort = aerr.abort |
|
|
|
@ -763,18 +694,18 @@ func (dl *diskLayer) generate(stats *generatorStats) { |
|
|
|
|
// Snapshot fully generated, set the marker to nil.
|
|
|
|
|
// Note even there is nothing to commit, persist the
|
|
|
|
|
// generator anyway to mark the snapshot is complete.
|
|
|
|
|
journalProgress(batch, nil, stats) |
|
|
|
|
if err := batch.Write(); err != nil { |
|
|
|
|
journalProgress(ctx.batch, nil, stats) |
|
|
|
|
if err := ctx.batch.Write(); err != nil { |
|
|
|
|
log.Error("Failed to flush batch", "err", err) |
|
|
|
|
|
|
|
|
|
abort = <-dl.genAbort |
|
|
|
|
abort <- stats |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
batch.Reset() |
|
|
|
|
ctx.batch.Reset() |
|
|
|
|
|
|
|
|
|
log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, |
|
|
|
|
"storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) |
|
|
|
|
"storage", stats.storage, "dangling", stats.dangling, "elapsed", common.PrettyDuration(time.Since(stats.start))) |
|
|
|
|
|
|
|
|
|
dl.lock.Lock() |
|
|
|
|
dl.genMarker = nil |
|
|
|
|