go-ethereum/cmd/geth/snapshot.go

1083 lines
34 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2021 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"slices"
"sort"
"syscall"
"time"
pebbleimpl "github.com/cockroachdb/pebble"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/pruner"
"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/pebble"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/urfave/cli/v2"
)
var (
snapshotCommand = &cli.Command{
Name: "snapshot",
Usage: "A set of commands based on the snapshot",
Description: "",
Subcommands: []*cli.Command{
{
Name: "prune-state",
Usage: "Prune stale ethereum state data based on the snapshot",
ArgsUsage: "<root>",
Action: pruneState,
Flags: slices.Concat([]cli.Flag{
utils.BloomFilterSizeFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot prune-state <state-root>
will prune historical state data with the help of the state snapshot.
All trie nodes and contract codes that do not belong to the specified
version state will be deleted from the database. After pruning, only
two version states are available: genesis and the specific one.
The default pruning target is the HEAD-127 state.
WARNING: it's only supported in hash mode(--state.scheme=hash)".
`,
},
{
Name: "verify-state",
Usage: "Recalculate state hash based on the snapshot for verification",
ArgsUsage: "<root>",
Action: verifyState,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot verify-state <state-root>
will traverse the whole accounts and storages set based on the specified
snapshot and recalculate the root hash of state for verification.
In other words, this command does the snapshot to trie conversion.
`,
},
{
Name: "generate-trie",
Usage: "Benchmark triedb.GenerateTrie against a hard-linked checkpoint of the chaindata",
ArgsUsage: "[<root>]",
Action: benchGenerateTrie,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags, []cli.Flag{
&cli.StringFlag{
Name: "checkpoint",
Usage: "Directory for the pebble checkpoint (default: <chaindata-parent>/.gentrie-bench-<ts>)",
},
&cli.BoolFlag{
Name: "keep",
Usage: "Keep the checkpoint directory after the run (debugging)",
},
}),
Description: `
geth snapshot generate-trie [<root>]
Takes a pebble checkpoint of the chaindata (hard-linked SST files, near-zero
disk usage and near-instant) and runs triedb.GenerateTrie against the
checkpoint. The source datadir is opened read-only for the checkpoint and
never written to. The checkpoint is removed on exit unless --keep is set,
including on Ctrl-C.
If <root> is not given, the head block's root is used.
`,
},
{
Name: "check-dangling-storage",
Usage: "Check that there is no 'dangling' snap storage",
ArgsUsage: "<root>",
Action: checkDanglingStorage,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot check-dangling-storage <state-root> traverses the snap storage
data, and verifies that all snapshot storage data has a corresponding account.
`,
},
{
Name: "inspect-account",
Usage: "Check all snapshot layers for the specific account",
ArgsUsage: "<address | hash>",
Action: checkAccount,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot inspect-account <address | hash> checks all snapshot layers and prints out
information about the specified address.
`,
},
{
Name: "traverse-state",
Usage: "Traverse the state with given root hash and perform quick verification",
ArgsUsage: "<root>",
Action: traverseState,
Flags: slices.Concat([]cli.Flag{
utils.AccountFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot traverse-state <state-root>
will traverse the whole state from the given state root and will abort if any
referenced trie node or contract code is missing. This command can be used for
state integrity verification. The default checking target is the HEAD state.
It's also usable without snapshot enabled.
If --account is specified, only the storage trie of that account is traversed.
`,
},
{
Name: "traverse-rawstate",
Usage: "Traverse the state with given root hash and perform detailed verification",
ArgsUsage: "<root>",
Action: traverseRawState,
Flags: slices.Concat([]cli.Flag{
utils.AccountFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot traverse-rawstate <state-root>
will traverse the whole state from the given root and will abort if any referenced
trie node or contract code is missing. This command can be used for state integrity
verification. The default checking target is the HEAD state. It's basically identical
to traverse-state, but the check granularity is smaller.
It's also usable without snapshot enabled.
If --account is specified, only the storage trie of that account is traversed.
`,
},
{
Name: "dump",
Usage: "Dump a specific block from storage (same as 'geth dump' but using snapshots)",
ArgsUsage: "[? <blockHash> | <blockNum>]",
Action: dumpState,
Flags: slices.Concat([]cli.Flag{
utils.ExcludeCodeFlag,
utils.ExcludeStorageFlag,
utils.StartKeyFlag,
utils.DumpLimitFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `
This command is semantically equivalent to 'geth dump', but uses the snapshots
as the backend data source, making this command a lot faster.
The argument is interpreted as block number or hash. If none is provided, the latest
block is used.
`,
},
{
Action: snapshotExportPreimages,
Name: "export-preimages",
Usage: "Export the preimage in snapshot enumeration order",
ArgsUsage: "<dumpfile> [<root>]",
Flags: utils.DatabaseFlags,
Description: `
The export-preimages command exports hash preimages to a flat file, in exactly
the expected order for the overlay tree migration.
`,
},
{
Name: "list-eip-7610-accounts",
Aliases: []string{"eip7610"},
Usage: "list EIP7610 eligible accounts",
Action: listEIP7610EligibleAccounts,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth snapshot list-eip-7610-accounts
traverses the postEIP-161 state and returns all accounts that are eligible
under EIP-7610: accounts with zero nonce, empty runtime code, and non-empty
storage. The traversal will be aborted immediately if the state is prior to
EIP-161.
The exported accounts are identified by their address.
`,
},
},
}
)
// Deprecation: this command should be deprecated once the hash-based
// scheme is deprecated.
func pruneState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, false)
defer chaindb.Close()
if rawdb.ReadStateScheme(chaindb) != rawdb.HashScheme {
log.Crit("Offline pruning is not required for path scheme")
}
prunerconfig := pruner.Config{
Datadir: stack.ResolvePath(""),
BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name),
}
pruner, err := pruner.NewPruner(chaindb, prunerconfig)
if err != nil {
log.Error("Failed to open snapshot tree", "err", err)
return err
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
var targetRoot common.Hash
if ctx.NArg() == 1 {
targetRoot, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "err", err)
return err
}
}
if err = pruner.Prune(targetRoot); err != nil {
log.Error("Failed to prune state", "err", err)
return err
}
return nil
}
func verifyState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close()
var (
err error
root = headBlock.Root()
)
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "err", err)
return err
}
}
if triedb.Scheme() == rawdb.PathScheme {
if err := triedb.VerifyState(root); err != nil {
log.Error("Failed to verify state", "root", root, "err", err)
return err
}
log.Info("Verified the state", "root", root)
// TODO(rjl493456442) implement dangling checks in pathdb.
return nil
} else {
snapConfig := snapshot.Config{
CacheSize: 256,
Recovery: false,
NoBuild: true,
AsyncBuild: false,
}
snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root())
if err != nil {
log.Error("Failed to open snapshot tree", "err", err)
return err
}
if err := snaptree.Verify(root); err != nil {
log.Error("Failed to verify state", "root", root, "err", err)
return err
}
log.Info("Verified the state", "root", root)
return snapshot.CheckDanglingStorage(chaindb)
}
}
// benchGenerateTrie runs triedb.GenerateTrie against a hard-linked checkpoint
// of the chaindata so the source datadir is never written to.
func benchGenerateTrie(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
// Resolve source chaindata path (handles network-specific subdirs).
srcDir := stack.ResolvePath("chaindata")
if fi, err := os.Stat(srcDir); err != nil {
return fmt.Errorf("chaindata not found at %s: %w", srcDir, err)
} else if !fi.IsDir() {
return fmt.Errorf("%s is not a directory", srcDir)
}
// Default to snapshot root, not head: that's what GenerateTrie actually
// reconstructs from flat state. On a fully-synced node they match.
var root common.Hash
if ctx.NArg() == 1 {
r, err := parseRoot(ctx.Args().First())
if err != nil {
return fmt.Errorf("parse root: %w", err)
}
root = r
} else {
chaindb := utils.MakeChainDatabase(ctx, stack, true)
snapRoot := rawdb.ReadSnapshotRoot(chaindb)
head := rawdb.ReadHeadBlock(chaindb)
chaindb.Close()
switch {
case snapRoot != (common.Hash{}):
root = snapRoot
log.Info("using snapshot root", "root", root)
case head != nil:
root = head.Root()
log.Info("using head block root", "number", head.Number(), "root", root)
default:
return errors.New("no snapshot or head block found; pass <root> explicitly")
}
}
// Default checkpoint sits next to chaindata so hard links work.
ckpt := ctx.String("checkpoint")
if ckpt == "" {
ts := time.Now().Format("20060102-150405")
ckpt = filepath.Join(filepath.Dir(srcDir), fmt.Sprintf(".gentrie-bench-%s", ts))
}
if _, err := os.Stat(ckpt); err == nil {
return fmt.Errorf("checkpoint dir %s already exists; remove it or pass --checkpoint to a fresh path", ckpt)
}
log.Info("creating pebble checkpoint", "src", srcDir, "dst", ckpt)
checkpointStart := time.Now()
if err := makeCheckpoint(srcDir, ckpt); err != nil {
return fmt.Errorf("checkpoint failed: %w", err)
}
log.Info("checkpoint created", "elapsed", time.Since(checkpointStart))
// Clean up the checkpoint on exit, including Ctrl-C.
keep := ctx.Bool("keep")
cleanup := func() {
if keep {
log.Info("keeping checkpoint", "path", ckpt)
return
}
log.Info("removing checkpoint", "path", ckpt)
if err := os.RemoveAll(ckpt); err != nil {
log.Error("failed to remove checkpoint", "err", err)
}
}
defer cleanup()
cancelCh := make(chan struct{})
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(sigCh)
go func() {
<-sigCh
log.Warn("interrupt received; cancelling GenerateTrie")
close(cancelCh)
}()
// Open the checkpoint writable. Reuse source ancient. Checkpoint only
// hard-links the pebble SSTs (not the freezer), and GenerateTrie never
// writes to ancient, so sharing it is safe.
srcAncient := stack.ResolveAncient("chaindata", "")
kv, err := pebble.New(ckpt, 4096, 1024, "gentrie-bench", false)
if err != nil {
return fmt.Errorf("open checkpoint: %w", err)
}
chaindb, err := rawdb.Open(kv, rawdb.OpenOptions{
Ancient: srcAncient,
MetricsNamespace: "gentrie-bench",
})
if err != nil {
kv.Close()
return fmt.Errorf("rawdb.Open checkpoint: %w", err)
}
defer chaindb.Close()
// Pick up the trie scheme already in use (path or hash).
triedbInst := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
scheme := triedbInst.Scheme()
triedbInst.Close()
log.Info("running GenerateTrie", "scheme", scheme, "root", root)
runStart := time.Now()
stats, err := triedb.GenerateTrie(chaindb, scheme, root, cancelCh)
elapsed := time.Since(runStart)
status := "root matched"
if err != nil {
status = fmt.Sprintf("failed (%s)", err)
log.Error("GenerateTrie failed", "elapsed", elapsed, "err", err)
}
fmt.Printf("\n=== generate-trie benchmark ===\n")
fmt.Printf("scheme: %s\n", scheme)
fmt.Printf("root: %s\n", root.Hex())
fmt.Printf("status: %s\n", status)
fmt.Printf("accounts: %d (%d updated)\n", stats.Scanned, stats.Updated)
fmt.Printf("wall time: %s\n", elapsed)
return err
}
// makeCheckpoint opens srcDir as a pebble database and writes a hard-linked
// checkpoint to dstDir. Source is closed on return.
//
// Opens read-write so pebble can finalize its startup (WAL replay, fresh
// OPTIONS file) before checkpointing. Read-only mode skips that step, and
// Checkpoint then fails trying to hard-link the missing OPTIONS file. The
// read-write open does no more than a normal geth startup would.
func makeCheckpoint(srcDir, dstDir string) error {
db, err := pebbleimpl.Open(srcDir, &pebbleimpl.Options{})
if err != nil {
return fmt.Errorf("open source pebble: %w", err)
}
defer db.Close()
return db.Checkpoint(dstDir)
}
// checkDanglingStorage iterates the snap storage data, and verifies that all
// storage also has corresponding account data.
func checkDanglingStorage(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
return snapshot.CheckDanglingStorage(db)
}
// parseAccount parses the account flag value as either an address (20 bytes)
// or an account hash (32 bytes) and returns the hashed account key.
func parseAccount(input string) (common.Hash, error) {
switch len(input) {
case 40, 42: // address
return crypto.Keccak256Hash(common.HexToAddress(input).Bytes()), nil
case 64, 66: // hash
return common.HexToHash(input), nil
default:
return common.Hash{}, errors.New("malformed account address or hash")
}
}
// lookupAccount resolves the account from the state trie using the given
// account hash.
func lookupAccount(accountHash common.Hash, tr *trie.Trie) (*types.StateAccount, error) {
accData, err := tr.Get(accountHash.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to get account %s: %w", accountHash, err)
}
if accData == nil {
return nil, fmt.Errorf("account not found: %s", accountHash)
}
var acc types.StateAccount
if err := rlp.DecodeBytes(accData, &acc); err != nil {
return nil, fmt.Errorf("invalid account data %s: %w", accountHash, err)
}
return &acc, nil
}
func traverseStorage(id *trie.ID, db *triedb.Database, report bool, detail bool) error {
tr, err := trie.NewStateTrie(id, db)
if err != nil {
log.Error("Failed to open storage trie", "account", id.Owner, "root", id.Root, "err", err)
return err
}
var (
slots int
nodes int
lastReport time.Time
start = time.Now()
)
it, err := tr.NodeIterator(nil)
if err != nil {
log.Error("Failed to open storage iterator", "account", id.Owner, "root", id.Root, "err", err)
return err
}
logger := log.Debug
if report {
logger = log.Info
}
logger("Start traversing storage trie", "account", id.Owner, "storageRoot", id.Root)
if !detail {
iter := trie.NewIterator(it)
for iter.Next() {
slots += 1
if time.Since(lastReport) > time.Second*8 {
logger("Traversing storage", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}
}
if iter.Err != nil {
log.Error("Failed to traverse storage trie", "root", id.Root, "err", iter.Err)
return iter.Err
}
logger("Storage is complete", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
} else {
reader, err := db.NodeReader(id.StateRoot)
if err != nil {
log.Error("Failed to open state reader", "err", err)
return err
}
var (
buffer = make([]byte, 32)
hasher = crypto.NewKeccakState()
)
for it.Next(true) {
nodes += 1
node := it.Hash()
// Check the presence for non-empty hash node(embedded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob, _ := reader.Node(id.Owner, it.Path(), node)
if len(blob) == 0 {
log.Error("Missing trie node(storage)", "hash", node)
return errors.New("missing storage")
}
hasher.Reset()
hasher.Write(blob)
hasher.Read(buffer)
if !bytes.Equal(buffer, node.Bytes()) {
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
return errors.New("invalid storage node")
}
}
if it.Leaf() {
slots += 1
}
if time.Since(lastReport) > time.Second*8 {
logger("Traversing storage", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}
}
if err := it.Error(); err != nil {
log.Error("Failed to traverse storage trie", "root", id.Root, "err", err)
return err
}
logger("Storage is complete", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
}
return nil
}
// traverseState is a helper function used for pruning verification.
// Basically it just iterates the trie, ensure all nodes and associated
// contract codes are present.
func traverseState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
var (
root common.Hash
err error
)
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "err", err)
return err
}
log.Info("Start traversing the state", "root", root)
} else {
root = headBlock.Root()
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
}
// If --account is specified, only traverse the storage trie of that account.
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
accountHash, err := parseAccount(accountStr)
if err != nil {
log.Error("Failed to parse account", "err", err)
return err
}
// Use raw trie since the account key is already hashed.
t, err := trie.New(trie.StateTrieID(root), triedb)
if err != nil {
log.Error("Failed to open state trie", "root", root, "err", err)
return err
}
acc, err := lookupAccount(accountHash, t)
if err != nil {
log.Error("Failed to look up account", "hash", accountHash, "err", err)
return err
}
if acc.Root == types.EmptyRootHash {
log.Info("Account has no storage", "hash", accountHash)
return nil
}
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, false)
}
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil {
log.Error("Failed to open trie", "root", root, "err", err)
return err
}
var (
accounts int
slots int
codes int
lastReport time.Time
start = time.Now()
)
acctIt, err := t.NodeIterator(nil)
if err != nil {
log.Error("Failed to open iterator", "root", root, "err", err)
return err
}
accIter := trie.NewIterator(acctIt)
for accIter.Next() {
accounts += 1
var acc types.StateAccount
if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
log.Error("Invalid account encountered during traversal", "err", err)
return err
}
if acc.Root != types.EmptyRootHash {
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root), triedb, false, false)
if err != nil {
return err
}
}
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
return errors.New("missing code")
}
codes += 1
}
if time.Since(lastReport) > time.Second*8 {
log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}
}
if accIter.Err != nil {
log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err)
return accIter.Err
}
log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
// traverseRawState is a helper function used for pruning verification.
// Basically it just iterates the trie, ensure all nodes and associated
// contract codes are present. It's basically identical to traverseState
// but it will check each trie node.
func traverseRawState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
var (
root common.Hash
err error
)
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "err", err)
return err
}
log.Info("Start traversing the state", "root", root)
} else {
root = headBlock.Root()
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
}
// If --account is specified, only traverse the storage trie of that account.
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
accountHash, err := parseAccount(accountStr)
if err != nil {
log.Error("Failed to parse account", "err", err)
return err
}
// Use raw trie since the account key is already hashed.
t, err := trie.New(trie.StateTrieID(root), triedb)
if err != nil {
log.Error("Failed to open state trie", "root", root, "err", err)
return err
}
acc, err := lookupAccount(accountHash, t)
if err != nil {
log.Error("Failed to look up account", "hash", accountHash, "err", err)
return err
}
if acc.Root == types.EmptyRootHash {
log.Info("Account has no storage", "hash", accountHash)
return nil
}
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, true)
}
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil {
log.Error("Failed to open trie", "root", root, "err", err)
return err
}
var (
nodes int
accounts int
slots int
codes int
lastReport time.Time
start = time.Now()
hasher = crypto.NewKeccakState()
got = make([]byte, 32)
)
accIter, err := t.NodeIterator(nil)
if err != nil {
log.Error("Failed to open iterator", "root", root, "err", err)
return err
}
reader, err := triedb.NodeReader(root)
if err != nil {
log.Error("State is non-existent", "root", root)
return nil
}
for accIter.Next(true) {
nodes += 1
node := accIter.Hash()
// Check the present for non-empty hash node(embedded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob, _ := reader.Node(common.Hash{}, accIter.Path(), node)
if len(blob) == 0 {
log.Error("Missing trie node(account)", "hash", node)
return errors.New("missing account")
}
hasher.Reset()
hasher.Write(blob)
hasher.Read(got)
if !bytes.Equal(got, node.Bytes()) {
log.Error("Invalid trie node(account)", "hash", node.Hex(), "value", blob)
return errors.New("invalid account node")
}
}
// If it's a leaf node, yes we are touching an account,
// dig into the storage trie further.
if accIter.Leaf() {
accounts += 1
var acc types.StateAccount
if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
log.Error("Invalid account encountered during traversal", "err", err)
return errors.New("invalid account")
}
if acc.Root != types.EmptyRootHash {
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root), triedb, false, true)
if err != nil {
return err
}
}
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey()))
return errors.New("missing code")
}
codes += 1
}
if time.Since(lastReport) > time.Second*8 {
log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}
}
}
if accIter.Error() != nil {
log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error())
return accIter.Error()
}
log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
func parseRoot(input string) (common.Hash, error) {
var h common.Hash
if err := h.UnmarshalText([]byte(input)); err != nil {
return h, err
}
return h, nil
}
func dumpState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
conf, root, err := parseDumpConfig(ctx, db)
if err != nil {
return err
}
triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false)
defer triedb.Close()
stateIt, err := utils.NewStateIterator(triedb, db, root)
if err != nil {
return err
}
accIt, err := stateIt.AccountIterator(root, common.BytesToHash(conf.Start))
if err != nil {
return err
}
defer accIt.Release()
log.Info("Snapshot dumping started", "root", root)
var (
start = time.Now()
logged = time.Now()
accounts uint64
)
enc := json.NewEncoder(os.Stdout)
enc.Encode(struct {
Root common.Hash `json:"root"`
}{root})
for accIt.Next() {
account, err := types.FullAccount(accIt.Account())
if err != nil {
return err
}
da := &state.DumpAccount{
Balance: account.Balance.String(),
Nonce: account.Nonce,
Root: account.Root.Bytes(),
CodeHash: account.CodeHash,
AddressHash: accIt.Hash().Bytes(),
}
if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
}
if !conf.SkipStorage {
da.Storage = make(map[common.Hash]string)
stIt, err := stateIt.StorageIterator(root, accIt.Hash(), common.Hash{})
if err != nil {
return err
}
for stIt.Next() {
da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot())
}
}
enc.Encode(da)
accounts++
if time.Since(logged) > 8*time.Second {
log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts,
"elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
if conf.Max > 0 && accounts >= conf.Max {
break
}
}
log.Info("Snapshot dumping complete", "accounts", accounts,
"elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
// snapshotExportPreimages dumps the preimage data to a flat file.
func snapshotExportPreimages(ctx *cli.Context) error {
if ctx.NArg() < 1 {
utils.Fatalf("This command requires an argument.")
}
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close()
var root common.Hash
if ctx.NArg() > 1 {
hash := ctx.Args().Get(1)
if !common.IsHexHash(hash) {
return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1))
}
root = common.HexToHash(hash)
} else {
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
root = headBlock.Root()
}
stateIt, err := utils.NewStateIterator(triedb, chaindb, root)
if err != nil {
return err
}
return utils.ExportSnapshotPreimages(chaindb, stateIt, ctx.Args().First(), root)
}
// checkAccount iterates the snap data layers, and looks up the given account
// across all layers.
func checkAccount(ctx *cli.Context) error {
if ctx.NArg() != 1 {
return errors.New("need <address|hash> arg")
}
var (
hash common.Hash
addr common.Address
)
switch arg := ctx.Args().First(); len(arg) {
case 40, 42:
addr = common.HexToAddress(arg)
hash = crypto.Keccak256Hash(addr.Bytes())
case 64, 66:
hash = common.HexToHash(arg)
default:
return errors.New("malformed address or hash")
}
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
start := time.Now()
log.Info("Checking difflayer journal", "address", addr, "hash", hash)
if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil {
return err
}
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
return nil
}
// listEIP7610EligibleAccounts traverses the postEIP-161 state and returns all
// accounts that are eligible under EIP-7610: accounts with zero nonce, empty
// runtime code, and non-empty storage.
//
// Such accounts could only have been created before EIP-161, since after that
// all newly created contracts are initialized with a nonce of one.
//
// This helper should be generally applicable to all networks, including the
// Ethereum mainnet. For most networks where EIP-161 was enabled from genesis,
// the resulting set is expected to be empty. Otherwise, network operators are
// responsible for generating the eligible account set themselves.
//
// Notably, the exported accounts are identified by their address.
func listEIP7610EligibleAccounts(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return nil
}
config, _, err := core.LoadChainConfig(chaindb, utils.MakeGenesis(ctx))
if err != nil {
log.Error("Failed to load chain config", "err", err)
return err
}
if !config.IsEIP158(headBlock.Number()) {
log.Info("Local head is prior to EIP-161", "head", headBlock.Number(), "eip-161", *config.EIP158Block)
return nil
}
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close()
if triedb.Scheme() != rawdb.PathScheme {
log.Error("Hash scheme is not supported")
return nil
}
iter, err := triedb.AccountIterator(headBlock.Root(), common.Hash{})
if err != nil {
log.Error("Failed to get account iterator", "err", err)
return err
}
var (
start = time.Now()
accounts []common.Address
)
for iter.Next() {
blob := iter.Account()
if blob == nil {
log.Error("Failed to get account blob")
return nil
}
var account types.SlimAccount
if err := rlp.DecodeBytes(blob, &account); err != nil {
log.Error("Failed to decode", "err", err)
return err
}
// EIP-7610 account eligibility:
// - account.nonce == 0
// - account.runtime_code == empty
// - account.storage != empty
if len(account.CodeHash) == 0 && account.Nonce == 0 && len(account.Root) != 0 {
preimage := rawdb.ReadPreimage(chaindb, iter.Hash())
if preimage == nil {
log.Error("Failed to read preimage", "hash", iter.Hash().Hex())
return nil
}
accounts = append(accounts, common.BytesToAddress(preimage))
}
}
if len(accounts) == 0 {
log.Info("Traversed state", "eligible", len(accounts), "elapsed", common.PrettyDuration(time.Since(start)))
} else {
sort.Slice(accounts, func(i, j int) bool {
return accounts[i].Cmp(accounts[j]) < 0
})
buf := make([]byte, len(accounts)*common.AddressLength)
for i, h := range accounts {
copy(buf[i*common.AddressLength:], h[:])
}
log.Info("Traversed state", "eligible", len(accounts), "elapsed", common.PrettyDuration(time.Since(start)), "output", hex.EncodeToString(buf))
}
return nil
}