Skip to content

Commit

Permalink
cmd: provide a new command to get all state root hash in path mode
Browse files Browse the repository at this point in the history
  • Loading branch information
VM committed Jan 24, 2024
1 parent 458d67c commit c0a47cd
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 107 deletions.
173 changes: 89 additions & 84 deletions cmd/geth/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import (
"sync/atomic"
"time"

"github.com/olekukonko/tablewriter"
"github.com/urfave/cli/v2"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand All @@ -46,7 +49,6 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"
"github.com/urfave/cli/v2"
)

var (
Expand Down Expand Up @@ -193,6 +195,21 @@ It's deprecated, please use "geth db export" instead.
}, utils.DatabasePathFlags),
Description: `
This command dumps out the state for a given block (or latest, if none provided).
If you use "dump" command in path mode, please firstly use "dump-roothash" command to get all available state root hash.
`,
}
dumpRootHashCommand = &cli.Command{
Action: dumpAllRootHashInPath,
Name: "dump-roothash",
Usage: "Dump all available state root hash in path mode",
Flags: flags.Merge([]cli.Flag{
utils.StateSchemeFlag,
}, utils.DatabasePathFlags),
Description: `
The dump-roothash command dump all available state root hash in path mode.
If you use "dump" command in path mode, please note that it only keeps at most 129 blocks which belongs to diffLayer or diskLayer.
Therefore, you must specify the blockNumber or blockHash that locates in diffLayer or diskLayer.
"geth" will print all available blockNumber and related block state root hash, and you can query block hash by block number.
`,
}
)
Expand Down Expand Up @@ -596,26 +613,6 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth
return nil, nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg())
}

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),
}

db := utils.MakeChainDatabase(ctx, stack, true, false)
provided, err := utils.CompareStateSchemeCLIWithConfig(ctx)
if err != nil {
Expand All @@ -625,85 +622,66 @@ func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, eth
if err != nil {
return nil, nil, common.Hash{}, err
}
var header *types.Header
if scheme == rawdb.PathScheme {
triedb := trie.NewDatabase(db, &trie.Config{PathDB: pathdb.ReadOnly})
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)
if header == nil {
return nil, nil, common.Hash{}, fmt.Errorf("no head block found")
}
if contain := triedb.ContainRootHash(header.Root); !contain {
return nil, nil, common.Hash{}, fmt.Errorf("PBSS doesn't contain specified MPT root hash for block hash %x", hash)
}
} else {
return nil, nil, common.Hash{}, fmt.Errorf("block hash %x not found", hash)
}

header := &types.Header{}
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 {
number, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
return nil, nil, common.Hash{}, err
}
if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) {
header = rawdb.ReadHeader(db, hash, number)
if header == nil {
return nil, nil, common.Hash{}, fmt.Errorf("no head block found")
}
if contain := triedb.ContainRootHash(header.Root); !contain {
return nil, nil, common.Hash{}, fmt.Errorf("PBSS doesn't contain specified MPT root hash for block number %d", number)
}
} else {
return nil, nil, common.Hash{}, fmt.Errorf("block number %x not found", number)
}
return nil, nil, common.Hash{}, fmt.Errorf("block %x not found", hash)
}
} else {
if stateRoot := triedb.Head(); stateRoot != (common.Hash{}) {
if contain := triedb.ContainRootHash(stateRoot); !contain {
return nil, nil, common.Hash{}, fmt.Errorf("PBSS doesn't contain specified state root %x", stateRoot)
}
conf.StateScheme = rawdb.PathScheme
log.Info("State dump configured", "mpt root hash", stateRoot, "skipcode", conf.SkipCode,
"skipstorage", conf.SkipStorage, "start", hexutil.Encode(conf.Start), "limit", conf.Max,
"state scheme", conf.StateScheme)
return conf, db, stateRoot, nil
number, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
return nil, nil, common.Hash{}, err
}
if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) {
header = rawdb.ReadHeader(db, hash, number)
} else {
return nil, nil, common.Hash{}, fmt.Errorf("no top state root hash in path db")
return nil, nil, common.Hash{}, fmt.Errorf("header for block %d not found", number)
}
}
} else {
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)
}
// Use latest
if scheme == rawdb.PathScheme {
triedb := trie.NewDatabase(db, &trie.Config{PathDB: pathdb.ReadOnly})
defer triedb.Close()
if stateRoot := triedb.Head(); stateRoot != (common.Hash{}) {
header.Root = stateRoot
} else {
number, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
return nil, nil, common.Hash{}, err
}
if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) {
header = rawdb.ReadHeader(db, hash, number)
} else {
return nil, nil, common.Hash{}, fmt.Errorf("header for block %d not found", number)
}
return nil, nil, common.Hash{}, fmt.Errorf("no top state root hash in path db")
}
} 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),
}
conf.StateScheme = scheme
log.Info("State dump configured", "block", header.Number, "hash", header.Hash().Hex(),
"skipcode", conf.SkipCode, "skipstorage", conf.SkipStorage, "start", hexutil.Encode(conf.Start),
Expand Down Expand Up @@ -739,6 +717,33 @@ func dump(ctx *cli.Context) error {
return nil
}

func dumpAllRootHashInPath(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, true, false)
defer db.Close()
triedb := trie.NewDatabase(db, &trie.Config{PathDB: pathdb.ReadOnly})
defer triedb.Close()

provided, err := utils.CompareStateSchemeCLIWithConfig(ctx)
if err != nil {
return err
}
scheme, err := rawdb.ParseStateScheme(provided, db)
if err != nil {
return err
}
if scheme == rawdb.HashScheme {
return errors.New("incorrect state scheme, you should use it in path mode")
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Block Number", "Block State Root Hash"})
table.AppendBulk(triedb.GetAllRooHash())
table.Render()
return nil
}

// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
_, err := strconv.Atoi(x)
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func init() {
removedbCommand,
dumpCommand,
dumpGenesisCommand,
dumpRootHashCommand,
// See accountcmd.go:
accountCommand,
walletCommand,
Expand Down
1 change: 0 additions & 1 deletion core/state/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ type DumpAccount struct {
Storage map[common.Hash]string `json:"storage,omitempty"`
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key

}

// Dump represents the full dump in a collected format, as one large map.
Expand Down
3 changes: 3 additions & 0 deletions eth/api_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
if stateDb == nil {
return state.Dump{}, errors.New("pending state is not available")
}
opts.StateScheme = stateDb.Database().TrieDB().Scheme()
return stateDb.RawDump(opts), nil
}
var header *types.Header
Expand All @@ -83,6 +84,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
if err != nil {
return state.Dump{}, err
}
opts.StateScheme = stateDb.Database().TrieDB().Scheme()
return stateDb.RawDump(opts), nil
}

Expand Down Expand Up @@ -188,6 +190,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
OnlyWithAddresses: !incompletes,
Start: start,
Max: uint64(maxResults),
StateScheme: stateDb.Database().TrieDB().Scheme(),
}
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
opts.Max = AccountRangeMaxResults
Expand Down
12 changes: 6 additions & 6 deletions trie/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,14 +365,14 @@ func (db *Database) Head() common.Hash {
return pdb.Head()
}

// ContainRootHash returns whether MPT root hash is existent.
// It's only supported by path-based database and will return false for
// GetAllHash returns all MPT root hash in diffLayer and diskLayer.
// It's only supported by path-based database and will return nil for
// others.
func (db *Database) ContainRootHash(root common.Hash) bool {
func (db *Database) GetAllRooHash() [][]string {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
log.Error("not supported")
return false
log.Error("Not supported")
return nil
}
return pdb.ContainRootHash(root)
return pdb.GetAllRooHash()
}
32 changes: 16 additions & 16 deletions trie/triedb/pathdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"errors"
"fmt"
"io"
"sort"
"strconv"
"sync"
"time"

Expand Down Expand Up @@ -442,25 +444,23 @@ func (db *Database) Head() common.Hash {
return db.tree.front()
}

// ContainRootHash returns whether MPT root hash is existent.
func (db *Database) ContainRootHash(root common.Hash) bool {
// GetAllRooHash returns all diffLayer and diskLayer root hash
func (db *Database) GetAllRooHash() [][]string {
db.lock.Lock()
defer db.lock.Unlock()

bottom := db.tree.bottom()
if l := db.tree.get(root); l != nil {
if l.rootHash() == bottom.rootHash() {
log.Info("root hash is equal to disk layer root")
} else {
log.Info("root hash locates in diff layer")
data := make([][]string, 0, len(db.tree.layers))
for _, v := range db.tree.layers {
if dl, ok := v.(*diffLayer); ok {
data = append(data, []string{fmt.Sprintf("%d", dl.block), dl.rootHash().String()})
}
return true
}
_, err := bottom.Node(common.Hash{}, []byte(""), root)
if err != nil {
log.Error("Failed to retrieve root hash in disk layer node buffer", "hash", root, "error", err)
return false
}
log.Info("root hash locates in disk layer node buffer")
return true
sort.Slice(data, func(i, j int) bool {
block1, _ := strconv.Atoi(data[i][0])
block2, _ := strconv.Atoi(data[j][0])
return block1 > block2
})

data = append(data, []string{"-1", db.tree.bottom().rootHash().String()})
return data
}

0 comments on commit c0a47cd

Please sign in to comment.