From a6a01109807eebd55206ff11c8c896ab4b2b03da Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Mon, 26 Jun 2017 12:12:35 +0100 Subject: [PATCH] cmd: Add support for exporting state and receipts --- cmd/geth/chaincmd.go | 19 +++++--- cmd/utils/cmd.go | 106 +++++++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index ab0e92f213..92635b896f 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -241,25 +241,32 @@ func exportChain(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) - chain, _ := utils.MakeChain(ctx, stack) + chain, chainDb := utils.MakeChain(ctx, stack) start := time.Now() var err error + var first, last int64 fp := ctx.Args().First() if len(ctx.Args()) < 3 { - err = utils.ExportChain(chain, fp) + first = 0 + last = int64(chain.CurrentBlock().NumberU64()) } else { // This can be improved to allow for numbers larger than 9223372036854775807 - first, ferr := strconv.ParseInt(ctx.Args().Get(1), 10, 64) - last, lerr := strconv.ParseInt(ctx.Args().Get(2), 10, 64) - if ferr != nil || lerr != nil { + first, err = strconv.ParseInt(ctx.Args().Get(1), 10, 64) + if err != nil { utils.Fatalf("Export error in parsing parameters: block number not an integer\n") } + + last, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64) + if err != nil { + utils.Fatalf("Export error in parsing parameters: block number not an integer\n") + } + if first < 0 || last < 0 { utils.Fatalf("Export error: block number must be greater than 0\n") } - err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last)) } + err = utils.ExportChain(chain, chainDb, fp, uint64(first), uint64(last)) if err != nil { utils.Fatalf("Export error: %v\n", err) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 17c258c6c8..8df5378fe2 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -18,6 +18,7 @@ package utils import ( + "archive/zip" "compress/gzip" "fmt" "io" @@ -26,8 +27,11 @@ import ( "runtime" "strings" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -171,29 +175,7 @@ func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { return true } -func ExportChain(blockchain *core.BlockChain, fn string) error { - log.Info("Exporting blockchain", "file", fn) - fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } - defer fh.Close() - - var writer io.Writer = fh - if strings.HasSuffix(fn, ".gz") { - writer = gzip.NewWriter(writer) - defer writer.(*gzip.Writer).Close() - } - - if err := blockchain.Export(writer); err != nil { - return err - } - log.Info("Exported blockchain", "file", fn) - - return nil -} - -func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, last uint64) error { +func ExportChain(blockchain *core.BlockChain, chainDb ethdb.Database, fn string, first uint64, last uint64) error { log.Info("Exporting blockchain", "file", fn) // TODO verify mode perms fh, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) @@ -208,9 +190,87 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las defer writer.(*gzip.Writer).Close() } + var z *zip.Writer + if strings.HasSuffix(fn, ".zip") { + z = zip.NewWriter(fh) + writer, err = z.Create("blocks.dat") + if err != nil { + return err + } + } + + log.Info("Writing blocks", "first", first, "last", last) if err := blockchain.ExportN(writer, first, last); err != nil { return err } + + if z != nil { + log.Info("Writing state", "blockNumber", last) + writer, err := z.Create("state.dat") + if err != nil { + return err + } + if err := exportState(blockchain, chainDb, writer, last); err != nil { + return err + } + + log.Info("Writing receipts", "blockNumber", last) + writer, err = z.Create("receipts.dat") + if err != nil { + return err + } + if err := exportReceipts(blockchain, chainDb, writer, last); err != nil { + return err + } + + err = z.Close() + if err != nil { + return err + } + } + log.Info("Exported blockchain to", "file", fn) return nil } + +func exportState(blockchain *core.BlockChain, chainDb ethdb.Database, writer io.Writer, blocknum uint64) error { + sdb, err := blockchain.StateAt(blockchain.GetHeaderByNumber(blocknum).Root) + if err != nil { + return err + } + + iter := state.NewNodeIterator(sdb) + nodes, hashnodes := 0, 0 + for iter.Next() { + nodes += 1 + if nodes%100000 == 0 { + log.Info("Exported nodes", "count", nodes, "hashnodes", hashnodes) + } + if iter.Hash != (common.Hash{}) { + hashnodes += 1 + entry, err := chainDb.Get(iter.Hash.Bytes()) + if err != nil { + return err + } + writer.Write(entry) + } + } + + return nil +} + +func exportReceipts(blockchain *core.BlockChain, chainDb ethdb.Database, writer io.Writer, blocknum uint64) error { + count := 0 + var i uint64 + for i = 0; i <= blocknum; i++ { + receipts := core.GetBlockReceipts(chainDb, blockchain.GetHeaderByNumber(i).Hash(), i) + count += len(receipts) + for _, receipt := range receipts { + receipt.EncodeRLP(writer) + } + if i%100000 == 0 { + log.Info("Exported receipts", "count", count, "blocknumber", i) + } + } + return nil +}