cmd/geth, eth, core: snapshot dump + unify with trie dump (#22795)
* cmd/geth, eth, core: snapshot dump + unify with trie dump * cmd/evm: dump API fixes * cmd/geth, core, eth: fix some remaining errors * cmd/evm: dump - add limit, support address startkey, address review concerns * cmd, core/state, eth: minor polishes, fix snap dump crash, unify format Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
committed by
GitHub
parent
1cca781a02
commit
addd8824cf
@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -27,12 +28,16 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
@ -152,20 +157,21 @@ The export-preimages command export hash preimages to an RLP encoded stream`,
|
||||
Action: utils.MigrateFlags(dump),
|
||||
Name: "dump",
|
||||
Usage: "Dump a specific block from storage",
|
||||
ArgsUsage: "[<blockHash> | <blockNum>]...",
|
||||
ArgsUsage: "[? <blockHash> | <blockNum>]",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.IterativeOutputFlag,
|
||||
utils.ExcludeCodeFlag,
|
||||
utils.ExcludeStorageFlag,
|
||||
utils.IncludeIncompletesFlag,
|
||||
utils.StartKeyFlag,
|
||||
utils.DumpLimitFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
The arguments are interpreted as block numbers or hashes.
|
||||
Use "ethereum dump 0" to dump the genesis block.`,
|
||||
This command dumps out the state for a given block (or latest, if none provided).
|
||||
`,
|
||||
}
|
||||
)
|
||||
|
||||
@ -373,47 +379,85 @@ func exportPreimages(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, ethdb.Database, common.Hash, error) {
|
||||
db := utils.MakeChainDatabase(ctx, stack, true)
|
||||
var header *types.Header
|
||||
if ctx.NArg() > 1 {
|
||||
return nil, nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg())
|
||||
}
|
||||
if ctx.NArg() == 1 {
|
||||
arg := ctx.Args().First()
|
||||
if hashish(arg) {
|
||||
hash := common.HexToHash(arg)
|
||||
if number := rawdb.ReadHeaderNumber(db, hash); number != nil {
|
||||
header = rawdb.ReadHeader(db, hash, *number)
|
||||
} else {
|
||||
return nil, nil, common.Hash{}, fmt.Errorf("block %x not found", hash)
|
||||
}
|
||||
} else {
|
||||
number, err := strconv.Atoi(arg)
|
||||
if err != nil {
|
||||
return nil, nil, common.Hash{}, err
|
||||
}
|
||||
if hash := rawdb.ReadCanonicalHash(db, uint64(number)); hash != (common.Hash{}) {
|
||||
header = rawdb.ReadHeader(db, hash, uint64(number))
|
||||
} else {
|
||||
return nil, nil, common.Hash{}, fmt.Errorf("header for block %d not found", number)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use latest
|
||||
header = rawdb.ReadHeadHeader(db)
|
||||
}
|
||||
if header == nil {
|
||||
return nil, nil, common.Hash{}, errors.New("no head block found")
|
||||
}
|
||||
startArg := common.FromHex(ctx.String(utils.StartKeyFlag.Name))
|
||||
var start common.Hash
|
||||
switch len(startArg) {
|
||||
case 0: // common.Hash
|
||||
case 32:
|
||||
start = common.BytesToHash(startArg)
|
||||
case 20:
|
||||
start = crypto.Keccak256Hash(startArg)
|
||||
log.Info("Converting start-address to hash", "address", common.BytesToAddress(startArg), "hash", start.Hex())
|
||||
default:
|
||||
return nil, nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg)
|
||||
}
|
||||
var conf = &state.DumpConfig{
|
||||
SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name),
|
||||
SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
|
||||
OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name),
|
||||
Start: start.Bytes(),
|
||||
Max: ctx.Uint64(utils.DumpLimitFlag.Name),
|
||||
}
|
||||
log.Info("State dump configured", "block", header.Number, "hash", header.Hash().Hex(),
|
||||
"skipcode", conf.SkipCode, "skipstorage", conf.SkipStorage,
|
||||
"start", hexutil.Encode(conf.Start), "limit", conf.Max)
|
||||
return conf, db, header.Root, nil
|
||||
}
|
||||
|
||||
func dump(ctx *cli.Context) error {
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
db := utils.MakeChainDatabase(ctx, stack, true)
|
||||
for _, arg := range ctx.Args() {
|
||||
var header *types.Header
|
||||
if hashish(arg) {
|
||||
hash := common.HexToHash(arg)
|
||||
number := rawdb.ReadHeaderNumber(db, hash)
|
||||
if number != nil {
|
||||
header = rawdb.ReadHeader(db, hash, *number)
|
||||
}
|
||||
} else {
|
||||
number, _ := strconv.Atoi(arg)
|
||||
hash := rawdb.ReadCanonicalHash(db, uint64(number))
|
||||
if hash != (common.Hash{}) {
|
||||
header = rawdb.ReadHeader(db, hash, uint64(number))
|
||||
}
|
||||
}
|
||||
if header == nil {
|
||||
fmt.Println("{}")
|
||||
utils.Fatalf("block not found")
|
||||
} else {
|
||||
state, err := state.New(header.Root, state.NewDatabase(db), nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("could not create new state: %v", err)
|
||||
}
|
||||
excludeCode := ctx.Bool(utils.ExcludeCodeFlag.Name)
|
||||
excludeStorage := ctx.Bool(utils.ExcludeStorageFlag.Name)
|
||||
includeMissing := ctx.Bool(utils.IncludeIncompletesFlag.Name)
|
||||
if ctx.Bool(utils.IterativeOutputFlag.Name) {
|
||||
state.IterativeDump(excludeCode, excludeStorage, !includeMissing, json.NewEncoder(os.Stdout))
|
||||
} else {
|
||||
if includeMissing {
|
||||
fmt.Printf("If you want to include accounts with missing preimages, you need iterative output, since" +
|
||||
" otherwise the accounts will overwrite each other in the resulting mapping.")
|
||||
}
|
||||
fmt.Printf("%v %s\n", includeMissing, state.Dump(excludeCode, excludeStorage, false))
|
||||
}
|
||||
conf, db, root, err := parseDumpConfig(ctx, stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := state.New(root, state.NewDatabase(db), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Bool(utils.IterativeOutputFlag.Name) {
|
||||
state.IterativeDump(conf, json.NewEncoder(os.Stdout))
|
||||
} else {
|
||||
if conf.OnlyWithAddresses {
|
||||
fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+
|
||||
" otherwise the accounts will overwrite each other in the resulting mapping.")
|
||||
return fmt.Errorf("incompatible options")
|
||||
}
|
||||
fmt.Println(string(state.Dump(conf)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
@ -142,6 +144,31 @@ verification. The default checking target is the HEAD state. It's basically iden
|
||||
to traverse-state, but the check granularity is smaller.
|
||||
|
||||
It's also usable without snapshot enabled.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "dump",
|
||||
Usage: "Dump a specific block from storage (same as 'geth dump' but using snapshots)",
|
||||
ArgsUsage: "[? <blockHash> | <blockNum>]",
|
||||
Action: utils.MigrateFlags(dumpState),
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.AncientFlag,
|
||||
utils.RopstenFlag,
|
||||
utils.RinkebyFlag,
|
||||
utils.GoerliFlag,
|
||||
utils.ExcludeCodeFlag,
|
||||
utils.ExcludeStorageFlag,
|
||||
utils.StartKeyFlag,
|
||||
utils.DumpLimitFlag,
|
||||
},
|
||||
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.
|
||||
`,
|
||||
},
|
||||
},
|
||||
@ -430,3 +457,73 @@ func parseRoot(input string) (common.Hash, error) {
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func dumpState(ctx *cli.Context) error {
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
conf, db, root, err := parseDumpConfig(ctx, stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accIt, err := snaptree.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 := snapshot.FullAccount(accIt.Account())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
da := &state.DumpAccount{
|
||||
Balance: account.Balance.String(),
|
||||
Nonce: account.Nonce,
|
||||
Root: account.Root,
|
||||
CodeHash: account.CodeHash,
|
||||
SecureKey: accIt.Hash().Bytes(),
|
||||
}
|
||||
if !conf.SkipCode && !bytes.Equal(account.CodeHash, emptyCode) {
|
||||
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
|
||||
}
|
||||
if !conf.SkipStorage {
|
||||
da.Storage = make(map[common.Hash]string)
|
||||
|
||||
stIt, err := snaptree.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
|
||||
}
|
||||
|
Reference in New Issue
Block a user