Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/state/snapshot: less disk reads #6

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
d1eb592
eth/protocols: persist received state segments
rjl493456442 Mar 5, 2021
48db911
core: initial implementation
rjl493456442 Mar 8, 2021
1b947e6
core/state/snapshot: add tests
rjl493456442 Mar 9, 2021
66b6b2b
core, eth: updates
rjl493456442 Mar 9, 2021
6dd40fd
eth/protocols/snapshot: count flat state size
rjl493456442 Mar 9, 2021
7ab41e9
core/state: add metrics
rjl493456442 Mar 9, 2021
656e92d
core/state/snapshot: skip unnecessary deletion
rjl493456442 Mar 9, 2021
6546af7
core/state/snapshot: rename
rjl493456442 Mar 9, 2021
510c00b
core/state/snapshot: use the global batch
rjl493456442 Mar 9, 2021
756f0c8
core/state/snapshot: add logs and fix wiping
rjl493456442 Mar 10, 2021
db37731
core/state/snapshot: fix
rjl493456442 Mar 10, 2021
0a7562d
core/state/snapshot: save generation progress even if the batch is empty
rjl493456442 Mar 10, 2021
961ea1a
core/state/snapshot: fixes
rjl493456442 Mar 10, 2021
d92e8b5
core/state/snapshot: fix initial account range length
rjl493456442 Mar 10, 2021
554071f
core/state/snapshot: fix initial account range
rjl493456442 Mar 11, 2021
2f14a15
eth/protocols/snap: store flat states during the healing
rjl493456442 Mar 11, 2021
7e2094a
eth/protocols/snap: print logs
rjl493456442 Mar 12, 2021
84b15a8
core/state/snapshot: refactor (#4)
holiman Mar 12, 2021
fe5bfb2
core, eth: fixes
rjl493456442 Mar 12, 2021
05eb370
core, eth: fix healing writer
rjl493456442 Mar 12, 2021
820467d
core, trie, eth: fix paths
rjl493456442 Mar 12, 2021
8fec107
eth/protocols/snap: fix encoding
rjl493456442 Mar 12, 2021
90c342b
eth, core: add debug log
rjl493456442 Mar 15, 2021
0362f6a
core/state/generate: release iterator asap (#5)
holiman Mar 15, 2021
69301f9
core/state/snapshot: optimize stats counter
rjl493456442 Mar 15, 2021
6b2a4d4
core, eth: add metric
rjl493456442 Mar 15, 2021
3bf34fa
core/state/snapshot: update comments
rjl493456442 Mar 15, 2021
81ed2d2
core/state/snapshot: improve tests
rjl493456442 Mar 15, 2021
c184620
core/state/snapshot: replace secure trie with standard trie
rjl493456442 Mar 16, 2021
c50637a
core/state/snapshot: wrap return as the struct
rjl493456442 Mar 16, 2021
c6eed05
core/state/snapshot: skip wiping correct states
rjl493456442 Mar 16, 2021
de503e3
core/state/snapshot: updates
rjl493456442 Mar 16, 2021
e39b025
core/state/snapshot: fixes
rjl493456442 Mar 16, 2021
a380986
core/state/snapshot: fix panic due to reference flaw in closure
holiman Mar 16, 2021
4944db4
core/state/snapshot: fix errors in state generation logic + fix log o…
holiman Mar 16, 2021
70d485a
core/state/snapshot: remove an error case
holiman Mar 16, 2021
dbad05e
core/state/snapshot: fix condition-check for exhausted snap state
holiman Mar 16, 2021
07d882e
core/state/snapshot: use stackTrie for small tries
holiman Mar 16, 2021
2586f31
core/state/snapshot: don't resolve small storage tries in vain
holiman Mar 16, 2021
05994ac
core/state/snapshot: properly clean up storage of deleted accounts
holiman Mar 16, 2021
f658782
core/state/snapshot: avoid RLP-encoding in some cases + minor nitpicks
holiman Mar 16, 2021
29584d7
core/state/snapshot: fix error (+testcase)
holiman Mar 16, 2021
400eb1b
core/state/snapshot: clean up tests a bit
holiman Mar 16, 2021
bfaa711
core/state/snapshot: work in progress on better tests
holiman Mar 16, 2021
a978fcd
core/state/snapshot: polish code
rjl493456442 Mar 17, 2021
d1de96b
core/state/snapshot: fix trie iteration abortion trigger
rjl493456442 Mar 17, 2021
b1919c9
core/state/snapshot: fixes flaws
rjl493456442 Mar 17, 2021
05c1652
core/state/snapshot: remove panic
rjl493456442 Mar 17, 2021
2c26c79
core/state/snapshot: fix abort
rjl493456442 Mar 17, 2021
93472dd
core/state/snapshot: more tests (plus failing testcase)
holiman Mar 17, 2021
d4d89d2
core/state/snapshot: more testcases + fix for failing test
holiman Mar 17, 2021
20e19ec
core/state/snapshot: testcase for malformed data
holiman Mar 17, 2021
2bd65b6
core/state/snapshot: some test nitpicks
holiman Mar 17, 2021
f412992
core/state/snapshot: improvements to logging
holiman Mar 17, 2021
16de86c
core/state/snapshot: testcase to demo error in abortion
holiman Mar 18, 2021
764969d
core/state/snapshot: fix abortion
rjl493456442 Mar 18, 2021
cae0cf2
cmd/geth: make verify-state report the root
holiman Mar 18, 2021
0c7cd77
trie: fix failing test
holiman Mar 18, 2021
f549b74
core/state/snapshot: add timer metrics
rjl493456442 Mar 18, 2021
ef79890
core/state/snapshot: fix metrics
rjl493456442 Mar 19, 2021
23aefe3
core/state/snapshot: udpate tests
rjl493456442 Mar 19, 2021
0ca10ea
eth/protocols/snap: write snapshot account even if code or state is n…
holiman Mar 26, 2021
b0fd55b
core/state/snapshot: fix diskmore check
holiman Mar 26, 2021
56059df
core/state/snapshot: review fixes
holiman Mar 26, 2021
9d2ec41
poc: another attempt at reducing the lookups
holiman Mar 18, 2021
1ea8978
squashme: remove some debug output
holiman Mar 29, 2021
0501b13
core/state/snapshot: some minor fixes
holiman Mar 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/geth/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ func verifyState(ctx *cli.Context) error {
}
}
if err := snaptree.Verify(root); err != nil {
log.Error("Failed to verfiy state", "error", err)
log.Error("Failed to verfiy state", "root", root, "error", err)
return err
}
log.Info("Verified the state")
log.Info("Verified the state", "root", root)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion core/state/snapshot/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash,
return
}
if !bytes.Equal(account.Root, subroot.Bytes()) {
results <- fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot)
results <- fmt.Errorf("invalid subroot(%x), want %x, got %x", hash, account.Root, subroot)
return
}
results <- nil
Expand Down
671 changes: 540 additions & 131 deletions core/state/snapshot/generate.go

Large diffs are not rendered by default.

649 changes: 646 additions & 3 deletions core/state/snapshot/generate_test.go

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions core/state/snapshot/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ const journalVersion uint64 = 0

// 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
// Indicator that whether the database was in progress of being wiped.
// It's deprecated but keep it here for background compatibility.
Wiping bool

Done bool // Whether the generator finished creating the snapshot
Marker []byte
Accounts uint64
Expand Down Expand Up @@ -193,14 +196,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
}
// 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 {
Expand All @@ -214,7 +209,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
origin = binary.BigEndian.Uint64(generator.Marker)
}
go base.generate(&generatorStats{
wiping: wiper,
origin: origin,
start: time.Now(),
accounts: generator.Accounts,
Expand Down Expand Up @@ -381,7 +375,6 @@ func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) {
Marker: dl.genMarker,
}
if stats != nil {
entry.Wiping = (stats.wiping != nil)
entry.Accounts = stats.accounts
entry.Slots = stats.slots
entry.Storage = uint64(stats.storage)
Expand Down
11 changes: 2 additions & 9 deletions core/state/snapshot/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,21 +637,14 @@ func (t *Tree) Rebuild(root common.Hash) {
// building a brand new snapshot.
rawdb.DeleteSnapshotRecoveryNumber(t.diskdb)

// Track whether there's a wipe currently running and keep it alive if so
var wiper chan struct{}

// Iterate over and mark all layers stale
for _, layer := range t.layers {
switch layer := layer.(type) {
case *diskLayer:
// If the base layer is generating, abort it and save
if layer.genAbort != nil {
abort := make(chan *generatorStats)
abort := make(chan *generatorStats, 1) // Discard the stats
layer.genAbort <- abort

if stats := <-abort; stats != nil {
wiper = stats.wiping
}
}
// Layer should be inactive now, mark it as stale
layer.lock.Lock()
Expand All @@ -672,7 +665,7 @@ func (t *Tree) Rebuild(root common.Hash) {
// generator will run a wiper first if there's not one running right now.
log.Info("Rebuilding state snapshot")
t.layers = map[common.Hash]snapshot{
root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper),
root: generateSnapshot(t.diskdb, t.triedb, t.cache, root),
}
}

Expand Down
32 changes: 24 additions & 8 deletions core/state/snapshot/wipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
)

// wipeSnapshot starts a goroutine to iterate over the entire key-value database
// and delete all the data associated with the snapshot (accounts, storage,
// 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{} {
Expand All @@ -53,10 +54,10 @@ func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} {
// 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 {
if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil, len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil {
return err
}
if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength); err != nil {
if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, nil, nil, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength, snapWipedStorageMeter, true); err != nil {
return err
}
// Compact the snapshot section of the database to get rid of unused space
Expand All @@ -82,8 +83,11 @@ func wipeContent(db ethdb.KeyValueStore) error {
}

// 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 {
// and having a specific total key length. The start and limit is optional for
// specifying a particular key range for deletion.
//
// Origin is included for wiping and limit is excluded if they are specified.
func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, origin []byte, limit []byte, keylen int, meter metrics.Meter, report bool) error {
// Batch deletions together to avoid holding an iterator for too long
var (
batch = db.NewBatch()
Expand All @@ -92,7 +96,11 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
// Iterate over the key-range and delete all of them
start, logged := time.Now(), time.Now()

it := db.NewIterator(prefix, nil)
it := db.NewIterator(prefix, origin)
var stop []byte
if limit != nil {
stop = append(prefix, limit...)
}
for it.Next() {
// Skip any keys with the correct prefix but wrong length (trie nodes)
key := it.Key()
Expand All @@ -102,6 +110,9 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
if len(key) != keylen {
continue
}
if stop != nil && bytes.Compare(key, stop) >= 0 {
break
}
// Delete the key and periodically recreate the batch and iterator
batch.Delete(key)
items++
Expand All @@ -116,7 +127,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
seekPos := key[len(prefix):]
it = db.NewIterator(prefix, seekPos)

if time.Since(logged) > 8*time.Second {
if time.Since(logged) > 8*time.Second && report {
log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
Expand All @@ -126,6 +137,11 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
if err := batch.Write(); err != nil {
return err
}
log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
if meter != nil {
meter.Mark(int64(items))
}
if report {
log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
}
return nil
}
2 changes: 1 addition & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
// The onleaf func is called _serially_, so we can reuse the same account
// for unmarshalling every time.
var account Account
root, err := s.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error {
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
Expand Down
24 changes: 19 additions & 5 deletions core/state/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,31 @@ import (
)

// NewStateSync create a new state trie download scheduler.
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom) *trie.Sync {
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom, onLeaf func(paths [][]byte, leaf []byte) error) *trie.Sync {
// Register the storage slot callback if the external callback is specified.
var onSlot func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error
if onLeaf != nil {
onSlot = func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error {
return onLeaf(paths, leaf)
}
}
// Register the account callback to connect the state trie and the storage
// trie belongs to the contract.
var syncer *trie.Sync
callback := func(path []byte, leaf []byte, parent common.Hash) error {
onAccount := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error {
if onLeaf != nil {
if err := onLeaf(paths, leaf); err != nil {
return err
}
}
var obj Account
if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
return err
}
syncer.AddSubTrie(obj.Root, path, parent, nil)
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent)
syncer.AddSubTrie(obj.Root, hexpath, parent, onSlot)
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), hexpath, parent)
return nil
}
syncer = trie.NewSync(root, database, callback, bloom)
syncer = trie.NewSync(root, database, onAccount, bloom)
return syncer
}
12 changes: 6 additions & 6 deletions core/state/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
// Tests that an empty state is not scheduled for syncing.
func TestEmptyStateSync(t *testing.T) {
empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()))
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()), nil)
if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 {
t.Errorf(" content requested for empty state: %v, %v, %v", nodes, paths, codes)
}
Expand Down Expand Up @@ -170,7 +170,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {

// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)

nodes, paths, codes := sched.Missing(count)
var (
Expand Down Expand Up @@ -249,7 +249,7 @@ func TestIterativeDelayedStateSync(t *testing.T) {

// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)

nodes, _, codes := sched.Missing(0)
queue := append(append([]common.Hash{}, nodes...), codes...)
Expand Down Expand Up @@ -297,7 +297,7 @@ func testIterativeRandomStateSync(t *testing.T, count int) {

// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)

queue := make(map[common.Hash]struct{})
nodes, _, codes := sched.Missing(count)
Expand Down Expand Up @@ -347,7 +347,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {

// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)

queue := make(map[common.Hash]struct{})
nodes, _, codes := sched.Missing(0)
Expand Down Expand Up @@ -414,7 +414,7 @@ func TestIncompleteStateSync(t *testing.T) {

// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)

var added []common.Hash

Expand Down
2 changes: 1 addition & 1 deletion eth/downloader/statesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
return &stateSync{
d: d,
root: root,
sched: state.NewStateSync(root, d.stateDB, d.stateBloom),
sched: state.NewStateSync(root, d.stateDB, d.stateBloom, nil),
keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState),
trieTasks: make(map[common.Hash]*trieTask),
codeTasks: make(map[common.Hash]*codeTask),
Expand Down
Loading