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.
This commit is contained in:
parent
381c66caf0
commit
e0a9752b96
@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
@ -71,6 +72,7 @@ Remove blockchain and state databases`,
|
|||||||
dbExportCmd,
|
dbExportCmd,
|
||||||
dbMetadataCmd,
|
dbMetadataCmd,
|
||||||
dbMigrateFreezerCmd,
|
dbMigrateFreezerCmd,
|
||||||
|
dbCheckStateContentCmd,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dbInspectCmd = cli.Command{
|
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",
|
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.`,
|
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: "<start (optional)>",
|
||||||
|
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{
|
dbStatCmd = cli.Command{
|
||||||
Action: utils.MigrateFlags(dbStats),
|
Action: utils.MigrateFlags(dbStats),
|
||||||
Name: "stats",
|
Name: "stats",
|
||||||
@ -289,6 +301,60 @@ func inspect(ctx *cli.Context) error {
|
|||||||
return rawdb.InspectDatabase(db, prefix, start)
|
return rawdb.InspectDatabase(db, prefix, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkStateContent(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
prefix []byte
|
||||||
|
start []byte
|
||||||
|
)
|
||||||
|
if ctx.NArg() > 1 {
|
||||||
|
return fmt.Errorf("Max 1 argument: %v", ctx.Command.ArgsUsage)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack, _ := makeConfigNode(ctx)
|
||||||
|
defer stack.Close()
|
||||||
|
|
||||||
|
db := utils.MakeChainDatabase(ctx, stack, true)
|
||||||
|
defer db.Close()
|
||||||
|
var (
|
||||||
|
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++
|
||||||
|
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(lastLog) > 8*time.Second {
|
||||||
|
log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", common.PrettyDuration(time.Since(startTime)))
|
||||||
|
lastLog = 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) {
|
func showLeveldbStats(db ethdb.KeyValueStater) {
|
||||||
if stats, err := db.Stat("leveldb.stats"); err != nil {
|
if stats, err := db.Stat("leveldb.stats"); err != nil {
|
||||||
log.Warn("Failed to read database stats", "error", err)
|
log.Warn("Failed to read database stats", "error", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user