Skip to content

Commit e0a9752

Browse files
authored
cmd/geth: add db check-state-content to verify integrity of trie nodes (#24840)
This PR adds db tooling (geth db check-state-content) to verify the integrity of trie nodes. It iterates through the 32-byte key space in the database, which is expected to contain RLP-encoded trie nodes, addressed by hash.
1 parent 381c66c commit e0a9752

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

cmd/geth/dbcmd.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/ethereum/go-ethereum/core/rawdb"
3636
"github.com/ethereum/go-ethereum/core/state/snapshot"
3737
"github.com/ethereum/go-ethereum/core/types"
38+
"github.com/ethereum/go-ethereum/crypto"
3839
"github.com/ethereum/go-ethereum/ethdb"
3940
"github.com/ethereum/go-ethereum/log"
4041
"github.com/ethereum/go-ethereum/trie"
@@ -71,6 +72,7 @@ Remove blockchain and state databases`,
7172
dbExportCmd,
7273
dbMetadataCmd,
7374
dbMigrateFreezerCmd,
75+
dbCheckStateContentCmd,
7476
},
7577
}
7678
dbInspectCmd = cli.Command{
@@ -83,6 +85,16 @@ Remove blockchain and state databases`,
8385
Usage: "Inspect the storage size for each type of data in the database",
8486
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
8587
}
88+
dbCheckStateContentCmd = cli.Command{
89+
Action: utils.MigrateFlags(checkStateContent),
90+
Name: "check-state-content",
91+
ArgsUsage: "<start (optional)>",
92+
Flags: utils.GroupFlags(utils.NetworkFlags, utils.DatabasePathFlags),
93+
Usage: "Verify that state data is cryptographically correct",
94+
Description: `This command iterates the entire database for 32-byte keys, looking for rlp-encoded trie nodes.
95+
For each trie node encountered, it checks that the key corresponds to the keccak256(value). If this is not true, this indicates
96+
a data corruption.`,
97+
}
8698
dbStatCmd = cli.Command{
8799
Action: utils.MigrateFlags(dbStats),
88100
Name: "stats",
@@ -289,6 +301,60 @@ func inspect(ctx *cli.Context) error {
289301
return rawdb.InspectDatabase(db, prefix, start)
290302
}
291303

304+
func checkStateContent(ctx *cli.Context) error {
305+
var (
306+
prefix []byte
307+
start []byte
308+
)
309+
if ctx.NArg() > 1 {
310+
return fmt.Errorf("Max 1 argument: %v", ctx.Command.ArgsUsage)
311+
}
312+
if ctx.NArg() > 0 {
313+
if d, err := hexutil.Decode(ctx.Args().First()); err != nil {
314+
return fmt.Errorf("failed to hex-decode 'start': %v", err)
315+
} else {
316+
start = d
317+
}
318+
}
319+
stack, _ := makeConfigNode(ctx)
320+
defer stack.Close()
321+
322+
db := utils.MakeChainDatabase(ctx, stack, true)
323+
defer db.Close()
324+
var (
325+
it = rawdb.NewKeyLengthIterator(db.NewIterator(prefix, start), 32)
326+
hasher = crypto.NewKeccakState()
327+
got = make([]byte, 32)
328+
errs int
329+
count int
330+
startTime = time.Now()
331+
lastLog = time.Now()
332+
)
333+
for it.Next() {
334+
count++
335+
v := it.Value()
336+
k := it.Key()
337+
hasher.Reset()
338+
hasher.Write(v)
339+
hasher.Read(got)
340+
if !bytes.Equal(k, got) {
341+
errs++
342+
fmt.Printf("Error at 0x%x\n", k)
343+
fmt.Printf(" Hash: 0x%x\n", got)
344+
fmt.Printf(" Data: 0x%x\n", v)
345+
}
346+
if time.Since(lastLog) > 8*time.Second {
347+
log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", common.PrettyDuration(time.Since(startTime)))
348+
lastLog = time.Now()
349+
}
350+
}
351+
if err := it.Error(); err != nil {
352+
return err
353+
}
354+
log.Info("Iterated the state content", "errors", errs, "items", count)
355+
return nil
356+
}
357+
292358
func showLeveldbStats(db ethdb.KeyValueStater) {
293359
if stats, err := db.Stat("leveldb.stats"); err != nil {
294360
log.Warn("Failed to read database stats", "error", err)

0 commit comments

Comments
 (0)