Skip to content

Commit c375ee9

Browse files
cmd/geth, core/state/snapshot: rework journal loading, implement account-check (ethereum#24765)
* cmd/geth, core/state/snapshot: rework journal loading, implement account-check * core/state/snapshot, cmd/geth: polish code (#37) * core/state/snapshot: minor nits * core/state/snapshot: simplify error logic * cmd/geth: go format Co-authored-by: rjl493456442 <[email protected]>
1 parent d6b5574 commit c375ee9

File tree

4 files changed

+205
-151
lines changed

4 files changed

+205
-151
lines changed

cmd/geth/snapshot.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ In other words, this command does the snapshot to trie conversion.
101101
Description: `
102102
geth snapshot check-dangling-storage <state-root> traverses the snap storage
103103
data, and verifies that all snapshot storage data has a corresponding account.
104+
`,
105+
},
106+
{
107+
Name: "inspect-account",
108+
Usage: "Check all snapshot layers for the a specific account",
109+
ArgsUsage: "<address | hash>",
110+
Action: utils.MigrateFlags(checkAccount),
111+
Category: "MISCELLANEOUS COMMANDS",
112+
Flags: utils.GroupFlags(utils.NetworkFlags, utils.DatabasePathFlags),
113+
Description: `
114+
geth snapshot inspect-account <address | hash> checks all snapshot layers and prints out
115+
information about the specified address.
104116
`,
105117
},
106118
{
@@ -535,3 +547,35 @@ func dumpState(ctx *cli.Context) error {
535547
"elapsed", common.PrettyDuration(time.Since(start)))
536548
return nil
537549
}
550+
551+
// checkAccount iterates the snap data layers, and looks up the given account
552+
// across all layers.
553+
func checkAccount(ctx *cli.Context) error {
554+
if ctx.NArg() != 1 {
555+
return errors.New("need <address|hash> arg")
556+
}
557+
var (
558+
hash common.Hash
559+
addr common.Address
560+
)
561+
switch len(ctx.Args()[0]) {
562+
case 40, 42:
563+
addr = common.HexToAddress(ctx.Args()[0])
564+
hash = crypto.Keccak256Hash(addr.Bytes())
565+
case 64, 66:
566+
hash = common.HexToHash(ctx.Args()[0])
567+
default:
568+
return errors.New("malformed address or hash")
569+
}
570+
stack, _ := makeConfigNode(ctx)
571+
defer stack.Close()
572+
chaindb := utils.MakeChainDatabase(ctx, stack, true)
573+
defer chaindb.Close()
574+
start := time.Now()
575+
log.Info("Checking difflayer journal", "address", addr, "hash", hash)
576+
if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil {
577+
return err
578+
}
579+
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
580+
return nil
581+
}

core/state/snapshot/generate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
171171
t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot)
172172
}
173173
if err := CheckDanglingStorage(snap.diskdb); err != nil {
174-
t.Fatalf("Detected dangling storages %v", err)
174+
t.Fatalf("Detected dangling storages: %v", err)
175175
}
176176
}
177177

core/state/snapshot/journal.go

Lines changed: 99 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -108,44 +108,15 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou
108108
// So if there is no journal, or the journal is invalid(e.g. the journal
109109
// is not matched with disk layer; or the it's the legacy-format journal,
110110
// etc.), we just discard all diffs and try to recover them later.
111-
journal := rawdb.ReadSnapshotJournal(db)
112-
if len(journal) == 0 {
113-
log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing")
114-
return base, generator, nil
115-
}
116-
r := rlp.NewStream(bytes.NewReader(journal), 0)
117-
118-
// Firstly, resolve the first element as the journal version
119-
version, err := r.Uint()
111+
var current snapshot = base
112+
err := iterateJournal(db, func(parent common.Hash, root common.Hash, destructSet map[common.Hash]struct{}, accountData map[common.Hash][]byte, storageData map[common.Hash]map[common.Hash][]byte) error {
113+
current = newDiffLayer(current, root, destructSet, accountData, storageData)
114+
return nil
115+
})
120116
if err != nil {
121-
log.Warn("Failed to resolve the journal version", "error", err)
122117
return base, generator, nil
123118
}
124-
if version != journalVersion {
125-
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
126-
return base, generator, nil
127-
}
128-
// Secondly, resolve the disk layer root, ensure it's continuous
129-
// with disk layer. Note now we can ensure it's the snapshot journal
130-
// correct version, so we expect everything can be resolved properly.
131-
var root common.Hash
132-
if err := r.Decode(&root); err != nil {
133-
return nil, journalGenerator{}, errors.New("missing disk layer root")
134-
}
135-
// The diff journal is not matched with disk, discard them.
136-
// It can happen that Geth crashes without persisting the latest
137-
// diff journal.
138-
if !bytes.Equal(root.Bytes(), base.root.Bytes()) {
139-
log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched")
140-
return base, generator, nil
141-
}
142-
// Load all the snapshot diffs from the journal
143-
snapshot, err := loadDiffLayer(base, r)
144-
if err != nil {
145-
return nil, journalGenerator{}, err
146-
}
147-
log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root())
148-
return snapshot, generator, nil
119+
return current, generator, nil
149120
}
150121

151122
// loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
@@ -218,57 +189,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
218189
return snapshot, false, nil
219190
}
220191

221-
// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
222-
// diff and verifying that it can be linked to the requested parent.
223-
func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
224-
// Read the next diff journal entry
225-
var root common.Hash
226-
if err := r.Decode(&root); err != nil {
227-
// The first read may fail with EOF, marking the end of the journal
228-
if err == io.EOF {
229-
return parent, nil
230-
}
231-
return nil, fmt.Errorf("load diff root: %v", err)
232-
}
233-
var destructs []journalDestruct
234-
if err := r.Decode(&destructs); err != nil {
235-
return nil, fmt.Errorf("load diff destructs: %v", err)
236-
}
237-
destructSet := make(map[common.Hash]struct{})
238-
for _, entry := range destructs {
239-
destructSet[entry.Hash] = struct{}{}
240-
}
241-
var accounts []journalAccount
242-
if err := r.Decode(&accounts); err != nil {
243-
return nil, fmt.Errorf("load diff accounts: %v", err)
244-
}
245-
accountData := make(map[common.Hash][]byte)
246-
for _, entry := range accounts {
247-
if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
248-
accountData[entry.Hash] = entry.Blob
249-
} else {
250-
accountData[entry.Hash] = nil
251-
}
252-
}
253-
var storage []journalStorage
254-
if err := r.Decode(&storage); err != nil {
255-
return nil, fmt.Errorf("load diff storage: %v", err)
256-
}
257-
storageData := make(map[common.Hash]map[common.Hash][]byte)
258-
for _, entry := range storage {
259-
slots := make(map[common.Hash][]byte)
260-
for i, key := range entry.Keys {
261-
if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
262-
slots[key] = entry.Vals[i]
263-
} else {
264-
slots[key] = nil
265-
}
266-
}
267-
storageData[entry.Hash] = slots
268-
}
269-
return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
270-
}
271-
272192
// Journal terminates any in-progress snapshot generation, also implicitly pushing
273193
// the progress into the database.
274194
func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
@@ -345,3 +265,96 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
345265
log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
346266
return base, nil
347267
}
268+
269+
// journalCallback is a function which is invoked by iterateJournal, every
270+
// time a difflayer is loaded from disk.
271+
type journalCallback = func(parent common.Hash, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error
272+
273+
// iterateJournal iterates through the journalled difflayers, loading them from
274+
// the database, and invoking the callback for each loaded layer.
275+
// The order is incremental; starting with the bottom-most difflayer, going towards
276+
// the most recent layer.
277+
// This method returns error either if there was some error reading from disk,
278+
// OR if the callback returns an error when invoked.
279+
func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
280+
journal := rawdb.ReadSnapshotJournal(db)
281+
if len(journal) == 0 {
282+
log.Warn("Loaded snapshot journal", "diffs", "missing")
283+
return nil
284+
}
285+
r := rlp.NewStream(bytes.NewReader(journal), 0)
286+
// Firstly, resolve the first element as the journal version
287+
version, err := r.Uint()
288+
if err != nil {
289+
log.Warn("Failed to resolve the journal version", "error", err)
290+
return errors.New("failed to resolve journal version")
291+
}
292+
if version != journalVersion {
293+
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
294+
return errors.New("wrong journal version")
295+
}
296+
// Secondly, resolve the disk layer root, ensure it's continuous
297+
// with disk layer. Note now we can ensure it's the snapshot journal
298+
// correct version, so we expect everything can be resolved properly.
299+
var parent common.Hash
300+
if err := r.Decode(&parent); err != nil {
301+
return errors.New("missing disk layer root")
302+
}
303+
if baseRoot := rawdb.ReadSnapshotRoot(db); baseRoot != parent {
304+
log.Warn("Loaded snapshot journal", "diskroot", baseRoot, "diffs", "unmatched")
305+
return fmt.Errorf("mismatched disk and diff layers")
306+
}
307+
for {
308+
var (
309+
root common.Hash
310+
destructs []journalDestruct
311+
accounts []journalAccount
312+
storage []journalStorage
313+
destructSet = make(map[common.Hash]struct{})
314+
accountData = make(map[common.Hash][]byte)
315+
storageData = make(map[common.Hash]map[common.Hash][]byte)
316+
)
317+
// Read the next diff journal entry
318+
if err := r.Decode(&root); err != nil {
319+
// The first read may fail with EOF, marking the end of the journal
320+
if errors.Is(err, io.EOF) {
321+
return nil
322+
}
323+
return fmt.Errorf("load diff root: %v", err)
324+
}
325+
if err := r.Decode(&destructs); err != nil {
326+
return fmt.Errorf("load diff destructs: %v", err)
327+
}
328+
if err := r.Decode(&accounts); err != nil {
329+
return fmt.Errorf("load diff accounts: %v", err)
330+
}
331+
if err := r.Decode(&storage); err != nil {
332+
return fmt.Errorf("load diff storage: %v", err)
333+
}
334+
for _, entry := range destructs {
335+
destructSet[entry.Hash] = struct{}{}
336+
}
337+
for _, entry := range accounts {
338+
if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
339+
accountData[entry.Hash] = entry.Blob
340+
} else {
341+
accountData[entry.Hash] = nil
342+
}
343+
}
344+
for _, entry := range storage {
345+
slots := make(map[common.Hash][]byte)
346+
for i, key := range entry.Keys {
347+
if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
348+
slots[key] = entry.Vals[i]
349+
} else {
350+
slots[key] = nil
351+
}
352+
}
353+
storageData[entry.Hash] = slots
354+
}
355+
if err := callback(parent, root, destructSet, accountData, storageData); err != nil {
356+
return err
357+
}
358+
parent = root
359+
}
360+
}

0 commit comments

Comments
 (0)