From 793aa2a7d19ec1b61f9adf8579a02d1a14db3ac6 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 10 May 2022 08:44:25 +0200 Subject: [PATCH 1/4] cmd/geth: tooling to verify integrity of trie nodes --- cmd/geth/dbcmd.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index c7c73a23ebd7..32f2ae2e12dd 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" @@ -71,6 +72,7 @@ Remove blockchain and state databases`, dbExportCmd, dbMetadataCmd, dbMigrateFreezerCmd, + dbCheckStateContentCmd, }, } dbInspectCmd = cli.Command{ @@ -83,6 +85,16 @@ Remove blockchain and state databases`, Usage: "Inspect the storage size for each type of data in the database", 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.`, } + dbCheckStateContentCmd = cli.Command{ + Action: utils.MigrateFlags(checkStateContent), + Name: "check-state-content", + ArgsUsage: " ", + Flags: utils.GroupFlags(utils.NetworkFlags, utils.DatabasePathFlags), + Usage: "Verify that state data is cryptographically correct", + Description: `This command iterates the entire database for 32-byte keys, looking for rlp-encoded trie nodes. +For each trie node encountered, it checks that the key corresponds to the keccak256(value). If this is not true, this indicates +a data corruption.`, + } dbStatCmd = cli.Command{ Action: utils.MigrateFlags(dbStats), Name: "stats", @@ -289,6 +301,66 @@ func inspect(ctx *cli.Context) error { return rawdb.InspectDatabase(db, prefix, start) } +func checkStateContent(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 2 { + return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() >= 1 { + if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { + return fmt.Errorf("failed to hex-decode 'prefix': %v", err) + } else { + prefix = d + } + } + if ctx.NArg() >= 2 { + if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + it := rawdb.NewKeyLengthIterator(db.NewIterator(prefix, start), 32) + hasher := crypto.NewKeccakState() + t := time.Now() + var ( + got = make([]byte, 32) + errs int + count int + ) + for it.Next() { + count++ + v := it.Value() + k := it.Key() + hasher.Reset() + hasher.Write(v) + hasher.Read(got) + if !bytes.Equal(k, got) { + errs++ + fmt.Printf("Error at 0x%x\n", k) + fmt.Printf(" Hash: 0x%x\n", got) + fmt.Printf(" Data: 0x%x\n", v) + } + if time.Since(t) > 8*time.Second { + log.Info("Iterating the database", "at", k) + t = time.Now() + } + } + if err := it.Error(); err != nil { + return err + } + log.Info("Iterated the state content", "errors", errs, "items", count) + return nil +} + func showLeveldbStats(db ethdb.KeyValueStater) { if stats, err := db.Stat("leveldb.stats"); err != nil { log.Warn("Failed to read database stats", "error", err) From 2a6257d2bd813b60590892d6dface19843d7d46a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 10 May 2022 14:45:22 +0200 Subject: [PATCH 2/4] cmd/geth: output fixes --- cmd/geth/dbcmd.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 32f2ae2e12dd..211f8c8273d6 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -328,13 +328,14 @@ func checkStateContent(ctx *cli.Context) error { db := utils.MakeChainDatabase(ctx, stack, true) defer db.Close() - it := rawdb.NewKeyLengthIterator(db.NewIterator(prefix, start), 32) - hasher := crypto.NewKeccakState() - t := time.Now() var ( - got = make([]byte, 32) - errs int - count int + it = rawdb.NewKeyLengthIterator(db.NewIterator(prefix, start), 32) + hasher = crypto.NewKeccakState() + got = make([]byte, 32) + errs int + count int + startTime = time.Now() + lastLog = time.Now() ) for it.Next() { count++ @@ -349,9 +350,9 @@ func checkStateContent(ctx *cli.Context) error { fmt.Printf(" Hash: 0x%x\n", got) fmt.Printf(" Data: 0x%x\n", v) } - if time.Since(t) > 8*time.Second { - log.Info("Iterating the database", "at", k) - t = time.Now() + if time.Since(lastLog) > 8*time.Second { + log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", time.Since(startTime)) + lastLog = time.Now() } } if err := it.Error(); err != nil { From 919f61bab0b49fd78cc19b612143b05fc243f418 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 11 May 2022 09:42:47 +0200 Subject: [PATCH 3/4] cmd/geth: review fixes --- cmd/geth/dbcmd.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 211f8c8273d6..996e26551ecd 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -88,7 +88,7 @@ Remove blockchain and state databases`, dbCheckStateContentCmd = cli.Command{ Action: utils.MigrateFlags(checkStateContent), Name: "check-state-content", - ArgsUsage: " ", + ArgsUsage: "", Flags: utils.GroupFlags(utils.NetworkFlags, utils.DatabasePathFlags), Usage: "Verify that state data is cryptographically correct", Description: `This command iterates the entire database for 32-byte keys, looking for rlp-encoded trie nodes. @@ -306,18 +306,11 @@ func checkStateContent(ctx *cli.Context) error { prefix []byte start []byte ) - if ctx.NArg() > 2 { + if ctx.NArg() > 1 { return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) } - if ctx.NArg() >= 1 { - if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { - return fmt.Errorf("failed to hex-decode 'prefix': %v", err) - } else { - prefix = d - } - } - if ctx.NArg() >= 2 { - if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + if ctx.NArg() > 0 { + if d, err := hexutil.Decode(ctx.Args().First()); err != nil { return fmt.Errorf("failed to hex-decode 'start': %v", err) } else { start = d @@ -351,7 +344,7 @@ func checkStateContent(ctx *cli.Context) error { fmt.Printf(" Data: 0x%x\n", v) } if time.Since(lastLog) > 8*time.Second { - log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", time.Since(startTime)) + log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", common.PrettyDuration(time.Since(startTime))) lastLog = time.Now() } } From c4333e742877eaf3c850ac318935f8f2f730c2cd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 17 May 2022 10:04:44 +0200 Subject: [PATCH 4/4] Update cmd/geth/dbcmd.go --- cmd/geth/dbcmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 996e26551ecd..750be8572162 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -307,7 +307,7 @@ func checkStateContent(ctx *cli.Context) error { start []byte ) if ctx.NArg() > 1 { - return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + return fmt.Errorf("Max 1 argument: %v", ctx.Command.ArgsUsage) } if ctx.NArg() > 0 { if d, err := hexutil.Decode(ctx.Args().First()); err != nil {