From bb47b35b9f93f7b6d135e2d9eb75c4fcf3909313 Mon Sep 17 00:00:00 2001 From: VM Date: Wed, 24 Jan 2024 20:32:28 +0800 Subject: [PATCH] cmd: print all state root hash in path mode --- cmd/geth/chaincmd.go | 159 ++++++++++++++++----------------- cmd/geth/main.go | 1 + core/state/dump.go | 1 - eth/api_debug.go | 3 + trie/database.go | 12 +-- trie/triedb/pathdb/database.go | 32 +++---- 6 files changed, 102 insertions(+), 106 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index a66155109f..935c245e8d 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v2" ) @@ -193,6 +194,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 use geth dump-roothash 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. `, } ) @@ -596,27 +612,8 @@ 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) + defer db.Close() provided, err := utils.CompareStateSchemeCLIWithConfig(ctx) if err != nil { return nil, nil, common.Hash{}, err @@ -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), @@ -744,3 +722,18 @@ func hashish(x string) bool { _, err := strconv.Atoi(x) return err != 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() + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Block Number", "Block State Root Hash"}) + table.AppendBulk(triedb.GetAllRooHash()) + table.Render() + return nil +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 11acc9b3c6..f11d56d53a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -239,6 +239,7 @@ func init() { removedbCommand, dumpCommand, dumpGenesisCommand, + dumpRootHashCommand, // See accountcmd.go: accountCommand, walletCommand, diff --git a/core/state/dump.go b/core/state/dump.go index 35b475ff41..cf206030b5 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -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. diff --git a/eth/api_debug.go b/eth/api_debug.go index 6afa046787..8d6f454634 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -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 @@ -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 } @@ -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 diff --git a/trie/database.go b/trie/database.go index a22a971a78..df83dd081c 100644 --- a/trie/database.go +++ b/trie/database.go @@ -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() } diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go index 011dc2fce6..aacd6b9323 100644 --- a/trie/triedb/pathdb/database.go +++ b/trie/triedb/pathdb/database.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" "io" + "sort" + "strconv" "sync" "time" @@ -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 }