Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9107ee4
cmd, core, tests: initial state pruner
rjl493456442 Apr 21, 2020
ba8efc6
core: fix db inspector
rjl493456442 May 7, 2020
1467930
cmd/geth: add verify-state
rjl493456442 May 7, 2020
6d3f487
cmd/geth: add verification tool
rjl493456442 Jul 29, 2020
4d11dcd
core/rawdb: implement flatdb
rjl493456442 Aug 14, 2020
732d171
cmd, core: fix rebase
rjl493456442 Aug 27, 2020
e904d90
core/state: use new contract code layout
rjl493456442 Aug 27, 2020
8c0f30f
core/state/pruner: avoid deleting genesis state
rjl493456442 Aug 27, 2020
0b97ce3
cmd/geth: add helper function
rjl493456442 Aug 27, 2020
6d7e028
core, cmd: fix extract genesis
rjl493456442 Aug 27, 2020
1f08997
core: minor fixes
rjl493456442 Aug 28, 2020
5747c8f
all: update sum
rjl493456442 Oct 9, 2020
80f1576
contracts: remove useless
rjl493456442 Oct 9, 2020
394312f
core/state/snapshot: plugin stacktrie
rjl493456442 Oct 12, 2020
e29bba8
core: polish
rjl493456442 Aug 31, 2020
cdc2476
core/state/snapshot: iterate storage concurrently
rjl493456442 Sep 1, 2020
226fa8f
core/state/snapshot: fix iteration
rjl493456442 Sep 1, 2020
5eff18c
core: add comments
rjl493456442 Sep 2, 2020
d86e334
core/state/snapshot: polish code
rjl493456442 Oct 12, 2020
c1caa9a
core/state: polish
rjl493456442 Oct 12, 2020
3fe484c
core/state/snapshot: rebase
rjl493456442 Oct 13, 2020
ba02664
core/rawdb: add comments
rjl493456442 Oct 14, 2020
04c76bb
core/rawdb: fix tests
rjl493456442 Oct 14, 2020
8fe9b9f
core/rawdb: improve tests
rjl493456442 Oct 14, 2020
b6f8f89
core/state/snapshot: fix concurrent iteration
rjl493456442 Oct 15, 2020
82e8046
core/state: run pruning during the recovery
rjl493456442 Oct 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ func init() {
retestethCommand,
// See cmd/utils/flags_legacy.go
utils.ShowDeprecated,
// See snapshot.go
snapshotCommand,
}
sort.Sort(cli.CommandsByName(app.Commands))

Expand Down
369 changes: 369 additions & 0 deletions cmd/geth/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
// Copyright 2020 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"
"fmt"
"math/big"
"os"
"time"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
cli "gopkg.in/urfave/cli.v1"
)

var (
snapshotCommand = cli.Command{
Name: "snapshot",
Usage: "A set of commands based on the snapshot",
Category: "MISCELLANEOUS COMMANDS",
Description: "",
Subcommands: []cli.Command{
{
Name: "prune-state",
Usage: "Prune stale ethereum state data based on snapshot",
ArgsUsage: "<root>",
Action: utils.MigrateFlags(pruneState),
Category: "MISCELLANEOUS COMMANDS",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.RopstenFlag,
utils.RinkebyFlag,
utils.GoerliFlag,
utils.LegacyTestnetFlag,
},
Description: `
geth snapshot prune-state <state-root>
will prune historical state data with the help of state snapshot.
All trie nodes that do not belong to the specified version state
will be deleted from the database.
`,
},
{
Name: "verify-state",
Usage: "Recalculate state hash based on snapshot for verification",
ArgsUsage: "<root>",
Action: utils.MigrateFlags(verifyState),
Category: "MISCELLANEOUS COMMANDS",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.RopstenFlag,
utils.RinkebyFlag,
utils.GoerliFlag,
utils.LegacyTestnetFlag,
},
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.
`,
},
{
Name: "traverse-state",
Usage: "Traverse the state with given root hash for verification",
ArgsUsage: "<root>",
Action: utils.MigrateFlags(traverseState),
Category: "MISCELLANEOUS COMMANDS",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.RopstenFlag,
utils.RinkebyFlag,
utils.GoerliFlag,
utils.LegacyTestnetFlag,
},
Description: `
geth snapshot traverse-state <state-root>
will traverse the whole trie from the given root and will abort if any referenced
node is missing. This command can be used for trie integrity verification.
`,
},
{
Name: "traverse-rawstate",
Usage: "Traverse the state with given root hash for verification",
ArgsUsage: "<root>",
Action: utils.MigrateFlags(traverseRawState),
Category: "MISCELLANEOUS COMMANDS",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.RopstenFlag,
utils.RinkebyFlag,
utils.GoerliFlag,
utils.LegacyTestnetFlag,
},
Description: `
geth snapshot traverse-rawstate <state-root>
will traverse the whole trie from the given root and will abort if any referenced
node/code is missing. This command can be used for trie integrity verification.
It's basically identical to traverse-state, but the check granularity is smaller.
`,
},
},
}
)

func pruneState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

chain, chaindb := utils.MakeChain(ctx, stack, true)
defer chaindb.Close()

pruner, err := pruner.NewPruner(chaindb, chain.CurrentBlock().Root(), stack.ResolvePath(""))
if err != nil {
utils.Fatalf("Failed to open snapshot tree %v", err)
}
if ctx.NArg() > 1 {
utils.Fatalf("too many arguments given")
}
var root common.Hash
if ctx.NArg() == 1 {
root = common.HexToHash(ctx.Args()[0])
}
err = pruner.Prune(root)
if err != nil {
utils.Fatalf("Failed to prune state", "error", err)
}
return nil
}

func verifyState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

chain, chaindb := utils.MakeChain(ctx, stack, true)
defer chaindb.Close()

snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, chain.CurrentBlock().Root(), false, false)
if err != nil {
fmt.Println("Failed to open snapshot tree", "error", err)
return nil
}
if ctx.NArg() > 1 {
utils.Fatalf("too many arguments given")
}
var root = chain.CurrentBlock().Root()
if ctx.NArg() == 1 {
root = common.HexToHash(ctx.Args()[0])
}
if err := snapshot.VerifyState(snaptree, root); err != nil {
fmt.Println("Failed to verify state", "error", err)
} else {
fmt.Println("Verified the state")
}
return nil
}

var (
// emptyRoot is the known root hash of an empty trie.
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

// emptyCode is the known hash of the empty EVM bytecode.
emptyCode = crypto.Keccak256(nil)
)

// traverseState is a helper function used for pruning verification.
// Basically it just iterates the trie, ensure all nodes and assoicated
// contract codes are present.
func traverseState(ctx *cli.Context) error {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true)))
glogger.Verbosity(log.LvlInfo)
log.Root().SetHandler(glogger)

stack, _ := makeConfigNode(ctx)
defer stack.Close()

_, chaindb := utils.MakeChain(ctx, stack, true)
defer chaindb.Close()

if ctx.NArg() > 1 {
log.Crit("Too many arguments given")
}
var root = rawdb.ReadSnapshotRoot(chaindb)
if ctx.NArg() == 1 {
root = common.HexToHash(ctx.Args()[0])
}
t, err := trie.NewSecure(root, trie.NewDatabase(chaindb))
if err != nil {
log.Crit("Failed to open trie", "root", root, "error", err)
}
var (
accounts int
slots int
codes int
lastReport time.Time
start = time.Now()
)
accIter := trie.NewIterator(t.NodeIterator(nil))
for accIter.Next() {
accounts += 1
var acc struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
log.Crit("Invalid account encountered during traversal", "error", err)
}
if acc.Root != emptyRoot {
storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(chaindb))
if err != nil {
log.Crit("Failed to open storage trie", "root", acc.Root, "error", err)
}
storageIter := trie.NewIterator(storageTrie.NodeIterator(nil))
for storageIter.Next() {
slots += 1
}
if storageIter.Err != nil {
log.Crit("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Err)
}
}
if !bytes.Equal(acc.CodeHash, emptyCode) {
code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
if len(code) == 0 {
log.Crit("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
}
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.Crit("Failed to traverse state trie", "root", root, "error", 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 assoicated
// contract codes are present. It's basically identical to traverseState
// but it will check each trie node.
func traverseRawState(ctx *cli.Context) error {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true)))
glogger.Verbosity(log.LvlInfo)
log.Root().SetHandler(glogger)

stack, _ := makeConfigNode(ctx)
defer stack.Close()

_, chaindb := utils.MakeChain(ctx, stack, true)
defer chaindb.Close()

if ctx.NArg() > 1 {
log.Crit("Too many arguments given")
}
var root = rawdb.ReadSnapshotRoot(chaindb)
if ctx.NArg() == 1 {
root = common.HexToHash(ctx.Args()[0])
}
t, err := trie.NewSecure(root, trie.NewDatabase(chaindb))
if err != nil {
log.Crit("Failed to open trie", "root", root, "error", err)
}
log.Info("Opened the state trie", "root", root)
var (
nodes int
accounts int
slots int
codes int
lastReport time.Time
start = time.Now()
)
accIter := t.NodeIterator(nil)
for accIter.Next(true) {
nodes += 1
node := accIter.Hash()

if node != (common.Hash{}) {
// Check the present for non-empty hash node(embeded node doesn't
// have their own hash).
blob := rawdb.ReadTrieNode(chaindb, node)
if len(blob) == 0 {
log.Crit("Missing trie node(account)", "hash", 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 struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
log.Crit("Invalid account encountered during traversal", "error", err)
}
if acc.Root != emptyRoot {
storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(chaindb))
if err != nil {
log.Crit("Failed to open storage trie", "root", acc.Root, "error", err)
}
storageIter := storageTrie.NodeIterator(nil)
for storageIter.Next(true) {
nodes += 1
node := storageIter.Hash()

// Check the present for non-empty hash node(embeded node doesn't
// have their own hash).
if node != (common.Hash{}) {
blob := rawdb.ReadTrieNode(chaindb, node)
if len(blob) == 0 {
log.Crit("Missing trie node(storage)", "hash", node)
}
}
// Bump the counter if it's leaf node.
if storageIter.Leaf() {
slots += 1
}
}
if storageIter.Error() != nil {
log.Crit("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Error())
}
}
if !bytes.Equal(acc.CodeHash, emptyCode) {
code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
if len(code) == 0 {
log.Crit("Code is missing", "account", common.BytesToHash(accIter.LeafKey()))
}
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.Crit("Failed to traverse state trie", "root", root, "error", accIter.Error())
}
Comment on lines +364 to +366
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to return an error instead of just exiting on errors? (re this location and all others)

log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}
2 changes: 1 addition & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
}
// Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 {
bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait)
bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait, true)
}
// Take ownership of this particular state
go bc.update()
Expand Down
Loading