traverse with --account

Signed-off-by: jsvisa <delweng@gmail.com>
This commit is contained in:
jsvisa 2025-09-08 04:01:06 +00:00
parent cd54a41336
commit f7074e170c
2 changed files with 398 additions and 208 deletions

View file

@ -103,17 +103,24 @@ information about the specified address.
{ {
Name: "traverse-state", Name: "traverse-state",
Usage: "Traverse the state with given root hash and perform quick verification", Usage: "Traverse the state with given root hash and perform quick verification",
ArgsUsage: "<root> [accountHash|accountAddress]", ArgsUsage: "<root>",
Action: traverseState, Action: traverseState,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags), Flags: slices.Concat(utils.TraverseStateFlags, utils.NetworkFlags, utils.DatabaseFlags),
Description: ` Description: `
geth snapshot traverse-state <state-root> [accountHash|accountAddress] geth snapshot traverse-state [--account <account>] [--start <key>] [--limit <key>] <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.
If accountHash or accountAddress is provided, traversal will start from that specific account and continue through all subsequent accounts. 1. Traverse the whole state from the given state root:
The format is auto-detected: 40/42 chars for address, 64/66 chars for hash. - --start: starting account key (64/66 chars hex) [optional]
- --limit: ending account key (64/66 chars hex) [optional]
2. Traverse a specific account's storage:
- --account: account address (40/42 chars) or hash (64/66 chars) [required]
- --start: starting storage key (64/66 chars hex) [optional]
- --limit: ending storage key (64/66 chars hex) [optional]
The default checking state root is the HEAD state if not specified.
The command will abort if any referenced trie node or contract code is missing.
This can be used for state integrity verification. The default target is HEAD state.
It's also usable without snapshot enabled. It's also usable without snapshot enabled.
`, `,
@ -121,18 +128,26 @@ It's also usable without snapshot enabled.
{ {
Name: "traverse-rawstate", Name: "traverse-rawstate",
Usage: "Traverse the state with given root hash and perform detailed verification", Usage: "Traverse the state with given root hash and perform detailed verification",
ArgsUsage: "<root> [accountHash|accountAddress]", ArgsUsage: "<root>",
Action: traverseRawState, Action: traverseRawState,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags), Flags: slices.Concat(utils.TraverseStateFlags, utils.NetworkFlags, utils.DatabaseFlags),
Description: ` Description: `
geth snapshot traverse-rawstate <state-root> geth snapshot traverse-rawstate [--account <account>] [--start <key>] [--limit <key>] <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.
If accountHash or accountAddress is provided, traversal will start from that specific account and continue through all subsequent accounts. Similar to traverse-state but with more detailed verification at the trie node level.
The format is auto-detected: 40/42 chars for address, 64/66 chars for hash.
1. Traverse the whole state from the given state root:
- --start: starting account key (64/66 chars hex) [optional]
- --limit: ending account key (64/66 chars hex) [optional]
2. Traverse a specific account's storage:
- --account: account address (40/42 chars) or hash (64/66 chars) [required]
- --start: starting storage key (64/66 chars hex) [optional]
- --limit: ending storage key (64/66 chars hex) [optional]
The default checking state root is the HEAD state if not specified.
The command will abort if any referenced trie node or contract code is missing.
This can be used for state integrity verification. The default target is HEAD state.
It's also usable without snapshot enabled. It's also usable without snapshot enabled.
`, `,
@ -297,113 +312,188 @@ func traverseState(ctx *cli.Context) error {
return errors.New("no head block") return errors.New("no head block")
} }
root, startKey, err := parseTraverseArgs(ctx) config, err := parseTraverseArgs(ctx)
if err != nil { if err != nil {
return err return err
} }
if root == (common.Hash{}) { if config.root == (common.Hash{}) {
root = headBlock.Root() config.root = headBlock.Root()
} }
log.Info("Start traversing the state", "root", root.Hex(), "startKey", common.Bytes2Hex(startKey)) t, err := trie.NewStateTrie(trie.StateTrieID(config.root), triedb)
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil { if err != nil {
log.Error("Failed to open trie", "root", root, "err", err) log.Error("Failed to open trie", "root", config.root, "err", err)
return err return err
} }
var ( var (
accounts int accounts int
slots int slots int
codes int codes int
lastReport time.Time start = time.Now()
start = time.Now()
) )
acctIt, err := t.NodeIterator(startKey) go func() {
if err != nil { timer := time.NewTicker(time.Second * 8)
log.Error("Failed to open iterator", "root", root, "err", err) defer timer.Stop()
return err for range timer.C {
} log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
accIter := trie.NewIterator(acctIt) }
for accIter.Next() { }()
accounts += 1
var acc types.StateAccount if config.isAccount {
if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { log.Info("Start traversing storage trie", "root", config.root.Hex(), "account", config.account.Hex(), "startKey", common.Bytes2Hex(config.startKey), "limitKey", common.Bytes2Hex(config.limitKey))
log.Error("Invalid account encountered during traversal", "err", err)
acc, err := t.GetAccountByHash(config.account)
if err != nil {
log.Error("Get account failed", "account", config.account.Hex(), "err", err)
return err return err
} }
if acc.Root != types.EmptyRootHash {
id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return err
}
storageIt, err := storageTrie.NodeIterator(nil)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
storageIter := trie.NewIterator(storageIt)
for storageIter.Next() {
slots += 1
if time.Since(lastReport) > time.Second*8 { if acc.Root == types.EmptyRootHash {
log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Account has no storage")
lastReport = time.Now() return nil
}
id := trie.StorageTrieID(config.root, config.account, acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return err
}
storageIt, err := storageTrie.NodeIterator(config.startKey)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
storageIter := trie.NewIterator(storageIt)
for storageIter.Next() {
if config.limitKey != nil && bytes.Compare(storageIter.Key, config.limitKey) >= 0 {
break
}
slots += 1
log.Debug("Storage slot", "key", common.Bytes2Hex(storageIter.Key), "value", common.Bytes2Hex(storageIter.Value))
}
if storageIter.Err != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
return storageIter.Err
}
log.Info("Storage traversal complete", "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
} else {
log.Info("Start traversing state trie", "root", config.root.Hex(), "startKey", common.Bytes2Hex(config.startKey), "limitKey", common.Bytes2Hex(config.limitKey))
acctIt, err := t.NodeIterator(config.startKey)
if err != nil {
log.Error("Failed to open iterator", "root", config.root, "err", err)
return err
}
accIter := trie.NewIterator(acctIt)
for accIter.Next() {
if config.limitKey != nil && bytes.Compare(accIter.Key, config.limitKey) >= 0 {
break
}
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 {
id := trie.StorageTrieID(config.root, common.BytesToHash(accIter.Key), acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return err
}
storageIt, err := storageTrie.NodeIterator(nil)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
storageIter := trie.NewIterator(storageIt)
for storageIter.Next() {
slots += 1
}
if storageIter.Err != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
return storageIter.Err
} }
} }
if storageIter.Err != nil { if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
return storageIter.Err log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
return errors.New("missing code")
}
codes += 1
} }
} }
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { if accIter.Err != nil {
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { log.Error("Failed to traverse state trie", "root", config.root, "err", accIter.Err)
log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) return accIter.Err
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()
} }
log.Info("State traversal complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
} }
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
} }
func parseTraverseArgs(ctx *cli.Context) (root common.Hash, startKey []byte, err error) { type traverseConfig struct {
if ctx.NArg() > 2 { root common.Hash
err = errors.New("too many arguments") startKey []byte
return limitKey []byte
account common.Hash
isAccount bool
}
func parseTraverseArgs(ctx *cli.Context) (*traverseConfig, error) {
if ctx.NArg() > 1 {
return nil, errors.New("too many arguments, only <root> is required")
} }
if ctx.NArg() >= 1 { config := &traverseConfig{}
root, err = parseRoot(ctx.Args().First()) var err error
if ctx.NArg() == 1 {
config.root, err = parseRoot(ctx.Args().First())
if err != nil { if err != nil {
return return nil, err
} }
} }
if ctx.NArg() == 2 { if accountFlag := ctx.String("account"); accountFlag != "" {
arg := ctx.Args().Get(1) config.isAccount = true
switch len(arg) { switch len(accountFlag) {
case 40, 42: case 40, 42:
startKey = crypto.Keccak256Hash(common.HexToAddress(arg).Bytes()).Bytes() config.account = crypto.Keccak256Hash(common.HexToAddress(accountFlag).Bytes())
case 64, 66: case 64, 66:
startKey = common.HexToHash(arg).Bytes() config.account = common.HexToHash(accountFlag)
default: default:
err = errors.New("invalid account format: must be 40/42 chars for address or 64/66 chars for hash") return nil, errors.New("account must be 40/42 chars for address or 64/66 chars for hash")
return
} }
} }
return root, startKey, nil
if startFlag := ctx.String("start"); startFlag != "" {
if len(startFlag) == 64 || len(startFlag) == 66 {
config.startKey = common.HexToHash(startFlag).Bytes()
} else {
return nil, errors.New("start key must be 64/66 chars hex")
}
}
if limitFlag := ctx.String("limit"); limitFlag != "" {
if len(limitFlag) == 64 || len(limitFlag) == 66 {
config.limitKey = common.HexToHash(limitFlag).Bytes()
} else {
return nil, errors.New("limit key must be 64/66 chars hex")
}
}
return config, nil
} }
// traverseRawState is a helper function used for pruning verification. // traverseRawState is a helper function used for pruning verification.
@ -426,132 +516,216 @@ func traverseRawState(ctx *cli.Context) error {
return errors.New("no head block") return errors.New("no head block")
} }
root, startKey, err := parseTraverseArgs(ctx) config, err := parseTraverseArgs(ctx)
if err != nil { if err != nil {
log.Error("Failed to parse arguments", "err", err) log.Error("Failed to parse arguments", "err", err)
return err return err
} }
if config.root == (common.Hash{}) {
config.root = headBlock.Root()
}
t, err := trie.NewStateTrie(trie.StateTrieID(config.root), triedb)
if err != nil {
log.Error("Failed to open trie", "root", config.root, "err", err)
return err
}
log.Info("Start traversing the state", "root", root.Hex(), "startKey", common.Bytes2Hex(startKey))
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil {
log.Error("Failed to open trie", "root", root, "err", err)
return err
}
var ( var (
nodes int accounts int
accounts int nodes int
slots int slots int
codes int codes int
lastReport time.Time start = time.Now()
start = time.Now() hasher = crypto.NewKeccakState()
hasher = crypto.NewKeccakState() got = make([]byte, 32)
got = make([]byte, 32)
) )
accIter, err := t.NodeIterator(startKey)
if err != nil { go func() {
log.Error("Failed to open iterator", "root", root, "err", err) timer := time.NewTicker(time.Second * 8)
return err defer timer.Stop()
} for range timer.C {
reader, err := triedb.NodeReader(root) log.Info("Traversing rawstate", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
if err != nil { }
log.Error("State is non-existent", "root", root) }()
if config.isAccount {
log.Info("Start traversing storage trie (raw)", "root", config.root.Hex(), "account", config.account.Hex(), "startKey", common.Bytes2Hex(config.startKey), "limitKey", common.Bytes2Hex(config.limitKey))
acc, err := t.GetAccountByHash(config.account)
if err != nil {
log.Error("Get account failed", "account", config.account.Hex(), "err", err)
return err
}
if acc.Root == types.EmptyRootHash {
log.Info("Account has no storage")
return nil
}
// Traverse the storage trie with detailed verification
id := trie.StorageTrieID(config.root, config.account, acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return err
}
storageIter, err := storageTrie.NodeIterator(config.startKey)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
reader, err := triedb.NodeReader(config.root)
if err != nil {
log.Error("State is non-existent", "root", config.root)
return nil
}
for storageIter.Next(true) {
nodes += 1
node := storageIter.Hash()
// Check the presence for non-empty hash node(embedded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob, _ := reader.Node(config.account, storageIter.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(got)
if !bytes.Equal(got, node.Bytes()) {
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
return errors.New("invalid storage node")
}
}
// Bump the counter if it's leaf node.
if storageIter.Leaf() {
// Check if we've exceeded the limit key for storage
if config.limitKey != nil && bytes.Compare(storageIter.LeafKey(), config.limitKey) >= 0 {
break
}
slots += 1
log.Debug("Storage slot", "key", common.Bytes2Hex(storageIter.LeafKey()), "value", common.Bytes2Hex(storageIter.LeafBlob()))
}
}
if storageIter.Error() != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
return storageIter.Error()
}
log.Info("Storage traversal complete (raw)", "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
} else {
log.Info("Start traversing the state trie (raw)", "root", config.root.Hex(), "startKey", common.Bytes2Hex(config.startKey), "limitKey", common.Bytes2Hex(config.limitKey))
accIter, err := t.NodeIterator(config.startKey)
if err != nil {
log.Error("Failed to open iterator", "root", config.root, "err", err)
return err
}
reader, err := triedb.NodeReader(config.root)
if err != nil {
log.Error("State is non-existent", "root", config.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() {
// Check if we've exceeded the limit key for accounts
if config.limitKey != nil && bytes.Compare(accIter.LeafKey(), config.limitKey) >= 0 {
break
}
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 {
id := trie.StorageTrieID(config.root, common.BytesToHash(accIter.LeafKey()), acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return errors.New("missing storage trie")
}
storageIter, err := storageTrie.NodeIterator(nil)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
for storageIter.Next(true) {
nodes += 1
node := storageIter.Hash()
// Check the presence for non-empty hash node(embedded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.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(got)
if !bytes.Equal(got, node.Bytes()) {
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
return errors.New("invalid storage node")
}
}
// Bump the counter if it's leaf node.
if storageIter.Leaf() {
slots += 1
}
}
if storageIter.Error() != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
return storageIter.Error()
}
}
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 accIter.Error() != nil {
log.Error("Failed to traverse state trie", "root", config.root, "err", accIter.Error())
return accIter.Error()
}
log.Info("State traversal complete (raw)", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
return nil 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 {
id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return errors.New("missing storage trie")
}
storageIter, err := storageTrie.NodeIterator(nil)
if err != nil {
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
return err
}
for storageIter.Next(true) {
nodes += 1
node := storageIter.Hash()
// Check the presence for non-empty hash node(embedded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.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(got)
if !bytes.Equal(got, node.Bytes()) {
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
return errors.New("invalid storage node")
}
}
// Bump the counter if it's leaf node.
if storageIter.Leaf() {
slots += 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 storageIter.Error() != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
return storageIter.Error()
}
}
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) { func parseRoot(input string) (common.Hash, error) {

View file

@ -994,6 +994,22 @@ var (
StateSchemeFlag, StateSchemeFlag,
HttpHeaderFlag, HttpHeaderFlag,
} }
// TraverseStateFlags is the flag group of all state traversal flags.
TraverseStateFlags = []cli.Flag{
&cli.StringFlag{
Name: "account",
Usage: "Account address or hash to traverse storage for (enables account mode)",
},
&cli.StringFlag{
Name: "start",
Usage: "Starting key (account key for state mode, storage key for account mode)",
},
&cli.StringFlag{
Name: "limit",
Usage: "Ending key (account key for state mode, storage key for account mode)",
},
}
) )
// default account to prefund when running Geth in dev mode // default account to prefund when running Geth in dev mode