-
Notifications
You must be signed in to change notification settings - Fork 20.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core/state/snapshot: detect and clean up dangling storage snapshot in…
… generation (#24811) * core/state/snapshot: check dangling storages when generating snapshot * core/state/snapshot: polish * core/state/snapshot: wipe the last part of the dangling storages * core/state/snapshot: fix and add tests * core/state/snapshot: fix comment * README: remove mentions of fast sync (#24656) Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> * core, cmd: expose dangling storage detector for wider usage * core/state/snapshot: rename variable * core, ethdb: use global iterators for snapshot generation * core/state/snapshot: polish * cmd, core/state/snapshot: polish * core/state/snapshot: polish * Update core/state/snapshot/generate.go Co-authored-by: Martin Holst Swende <martin@swende.se> * ethdb: extend db test suite and fix memorydb iterator * ethdb/dbtest: rollback changes * ethdb/memorydb: simplify iteration * core/state/snapshot: update dangling counter * core/state/snapshot: release iterators * core/state/snapshot: update metrics * core/state/snapshot: update time metrics * metrics/influxdb: temp solution to present counter meaningfully, remove it * add debug log, revert later * core/state/snapshot: fix iterator panic * all: customized snapshot iterator for backward iteration * core, ethdb: polish * core/state/snapshot: remove debug log * core/state/snapshot: address comments from peter * core/state/snapshot: reopen the iterator at the next position * ethdb, core/state/snapshot: address comment from peter * core/state/snapshot: reopen exhausted iterators Co-authored-by: Tbnoapi <63448616+nuoomnoy02@users.noreply.github.com> Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Martin Holst Swende <martin@swende.se>
- Loading branch information
1 parent
2b0d0ce
commit 59ac229
Showing
13 changed files
with
965 additions
and
509 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
// Copyright 2022 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" | ||
"encoding/binary" | ||
"errors" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/math" | ||
"github.com/ethereum/go-ethereum/core/rawdb" | ||
"github.com/ethereum/go-ethereum/ethdb" | ||
"github.com/ethereum/go-ethereum/ethdb/memorydb" | ||
"github.com/ethereum/go-ethereum/log" | ||
) | ||
|
||
const ( | ||
snapAccount = "account" // Identifier of account snapshot generation | ||
snapStorage = "storage" // Identifier of storage snapshot generation | ||
) | ||
|
||
// 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) | ||
dangling uint64 // Number of dangling storage slots | ||
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, | ||
"dangling", gs.dangling, | ||
"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...) | ||
} | ||
|
||
// generatorContext carries a few global values to be shared by all generation functions. | ||
type generatorContext struct { | ||
stats *generatorStats // Generation statistic collection | ||
db ethdb.KeyValueStore // Key-value store containing the snapshot data | ||
account *holdableIterator // Iterator of account snapshot data | ||
storage *holdableIterator // Iterator of storage snapshot data | ||
batch ethdb.Batch // Database batch for writing batch data atomically | ||
logged time.Time // The timestamp when last generation progress was displayed | ||
} | ||
|
||
// newGeneratorContext initializes the context for generation. | ||
func newGeneratorContext(stats *generatorStats, db ethdb.KeyValueStore, accMarker []byte, storageMarker []byte) *generatorContext { | ||
ctx := &generatorContext{ | ||
stats: stats, | ||
db: db, | ||
batch: db.NewBatch(), | ||
logged: time.Now(), | ||
} | ||
ctx.openIterator(snapAccount, accMarker) | ||
ctx.openIterator(snapStorage, storageMarker) | ||
return ctx | ||
} | ||
|
||
// openIterator constructs global account and storage snapshot iterators | ||
// at the interrupted position. These iterators should be reopened from time | ||
// to time to avoid blocking leveldb compaction for a long time. | ||
func (ctx *generatorContext) openIterator(kind string, start []byte) { | ||
if kind == snapAccount { | ||
iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start) | ||
ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength)) | ||
return | ||
} | ||
iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start) | ||
ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength)) | ||
} | ||
|
||
// reopenIterator releases the specified snapshot iterator and re-open it | ||
// in the next position. It's aimed for not blocking leveldb compaction. | ||
func (ctx *generatorContext) reopenIterator(kind string) { | ||
// Shift iterator one more step, so that we can reopen | ||
// the iterator at the right position. | ||
var iter = ctx.account | ||
if kind == snapStorage { | ||
iter = ctx.storage | ||
} | ||
hasNext := iter.Next() | ||
if !hasNext { | ||
// Iterator exhausted, release forever and create an already exhausted virtual iterator | ||
iter.Release() | ||
if kind == snapAccount { | ||
ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) | ||
return | ||
} | ||
ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) | ||
return | ||
} | ||
next := iter.Key() | ||
iter.Release() | ||
ctx.openIterator(kind, next[1:]) | ||
} | ||
|
||
// close releases all the held resources. | ||
func (ctx *generatorContext) close() { | ||
ctx.account.Release() | ||
ctx.storage.Release() | ||
} | ||
|
||
// iterator returns the corresponding iterator specified by the kind. | ||
func (ctx *generatorContext) iterator(kind string) *holdableIterator { | ||
if kind == snapAccount { | ||
return ctx.account | ||
} | ||
return ctx.storage | ||
} | ||
|
||
// removeStorageBefore deletes all storage entries which are located before | ||
// the specified account. When the iterator touches the storage entry which | ||
// is located in or outside the given account, it stops and holds the current | ||
// iterated element locally. | ||
func (ctx *generatorContext) removeStorageBefore(account common.Hash) { | ||
var ( | ||
count uint64 | ||
start = time.Now() | ||
iter = ctx.storage | ||
) | ||
for iter.Next() { | ||
key := iter.Key() | ||
if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 { | ||
iter.Hold() | ||
break | ||
} | ||
count++ | ||
ctx.batch.Delete(key) | ||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize { | ||
ctx.batch.Write() | ||
ctx.batch.Reset() | ||
} | ||
} | ||
ctx.stats.dangling += count | ||
snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds()) | ||
} | ||
|
||
// removeStorageAt deletes all storage entries which are located in the specified | ||
// account. When the iterator touches the storage entry which is outside the given | ||
// account, it stops and holds the current iterated element locally. An error will | ||
// be returned if the initial position of iterator is not in the given account. | ||
func (ctx *generatorContext) removeStorageAt(account common.Hash) error { | ||
var ( | ||
count int64 | ||
start = time.Now() | ||
iter = ctx.storage | ||
) | ||
for iter.Next() { | ||
key := iter.Key() | ||
cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes()) | ||
if cmp < 0 { | ||
return errors.New("invalid iterator position") | ||
} | ||
if cmp > 0 { | ||
iter.Hold() | ||
break | ||
} | ||
count++ | ||
ctx.batch.Delete(key) | ||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize { | ||
ctx.batch.Write() | ||
ctx.batch.Reset() | ||
} | ||
} | ||
snapWipedStorageMeter.Mark(count) | ||
snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds()) | ||
return nil | ||
} | ||
|
||
// removeStorageLeft deletes all storage entries which are located after | ||
// the current iterator position. | ||
func (ctx *generatorContext) removeStorageLeft() { | ||
var ( | ||
count uint64 | ||
start = time.Now() | ||
iter = ctx.storage | ||
) | ||
for iter.Next() { | ||
count++ | ||
ctx.batch.Delete(iter.Key()) | ||
if ctx.batch.ValueSize() > ethdb.IdealBatchSize { | ||
ctx.batch.Write() | ||
ctx.batch.Reset() | ||
} | ||
} | ||