chaincmd.go 9.67 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2015 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
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
15
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
16

17 18 19 20 21 22
package main

import (
	"fmt"
	"os"
	"path/filepath"
23
	"runtime"
24
	"strconv"
25
	"sync/atomic"
26 27 28 29
	"time"

	"github.com/ethereum/go-ethereum/cmd/utils"
	"github.com/ethereum/go-ethereum/common"
30
	"github.com/ethereum/go-ethereum/console"
31 32 33
	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
34
	"github.com/ethereum/go-ethereum/ethdb"
35
	"github.com/ethereum/go-ethereum/logger"
36
	"github.com/ethereum/go-ethereum/logger/glog"
37
	"github.com/ethereum/go-ethereum/trie"
38
	"github.com/syndtr/goleveldb/leveldb/util"
39
	"gopkg.in/urfave/cli.v1"
40 41 42
)

var (
43 44 45 46 47 48 49 50 51 52 53 54
	initCommand = cli.Command{
		Action:    initGenesis,
		Name:      "init",
		Usage:     "Bootstrap and initialize a new genesis block",
		ArgsUsage: "<genesisPath>",
		Category:  "BLOCKCHAIN COMMANDS",
		Description: `
The init command initializes a new genesis block and definition for the network.
This is a destructive action and changes the network in which you will be
participating.
`,
	}
55
	importCommand = cli.Command{
56 57 58 59 60 61 62 63
		Action:    importChain,
		Name:      "import",
		Usage:     "Import a blockchain file",
		ArgsUsage: "<filename>",
		Category:  "BLOCKCHAIN COMMANDS",
		Description: `
TODO: Please write this
`,
64 65
	}
	exportCommand = cli.Command{
66 67 68 69 70
		Action:    exportChain,
		Name:      "export",
		Usage:     "Export blockchain into file",
		ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
		Category:  "BLOCKCHAIN COMMANDS",
71 72 73 74 75
		Description: `
Requires a first argument of the file to write to.
Optional second and third arguments control the first and
last block to write. In this mode, the file will be appended
if already existing.
76
`,
77 78
	}
	upgradedbCommand = cli.Command{
79 80 81 82 83 84 85 86
		Action:    upgradeDB,
		Name:      "upgradedb",
		Usage:     "Upgrade chainblock database",
		ArgsUsage: " ",
		Category:  "BLOCKCHAIN COMMANDS",
		Description: `
TODO: Please write this
`,
87 88
	}
	removedbCommand = cli.Command{
89 90 91 92 93 94 95 96
		Action:    removeDB,
		Name:      "removedb",
		Usage:     "Remove blockchain and state databases",
		ArgsUsage: " ",
		Category:  "BLOCKCHAIN COMMANDS",
		Description: `
TODO: Please write this
`,
97 98
	}
	dumpCommand = cli.Command{
99 100 101 102 103
		Action:    dump,
		Name:      "dump",
		Usage:     "Dump a specific block from storage",
		ArgsUsage: "[<blockHash> | <blockNum>]...",
		Category:  "BLOCKCHAIN COMMANDS",
104 105 106 107 108 109 110
		Description: `
The arguments are interpreted as block numbers or hashes.
Use "ethereum dump 0" to dump the genesis block.
`,
	}
)

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
// initGenesis will initialise the given JSON format genesis file and writes it as
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
func initGenesis(ctx *cli.Context) error {
	genesisPath := ctx.Args().First()
	if len(genesisPath) == 0 {
		utils.Fatalf("must supply path to genesis JSON file")
	}

	stack := makeFullNode(ctx)
	chaindb := utils.MakeChainDatabase(ctx, stack)

	genesisFile, err := os.Open(genesisPath)
	if err != nil {
		utils.Fatalf("failed to read genesis file: %v", err)
	}

	block, err := core.WriteGenesisBlock(chaindb, genesisFile)
	if err != nil {
		utils.Fatalf("failed to write genesis block: %v", err)
	}
	glog.V(logger.Info).Infof("successfully wrote genesis block and/or chain rule set: %x", block.Hash())
	return nil
}

135
func importChain(ctx *cli.Context) error {
136 137 138
	if len(ctx.Args()) != 1 {
		utils.Fatalf("This command requires an argument.")
	}
139 140
	stack := makeFullNode(ctx)
	chain, chainDb := utils.MakeChain(ctx, stack)
141 142
	defer chainDb.Close()

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
	// Start periodically gathering memory profiles
	var peakMemAlloc, peakMemSys uint64
	go func() {
		stats := new(runtime.MemStats)
		for {
			runtime.ReadMemStats(stats)
			if atomic.LoadUint64(&peakMemAlloc) < stats.Alloc {
				atomic.StoreUint64(&peakMemAlloc, stats.Alloc)
			}
			if atomic.LoadUint64(&peakMemSys) < stats.Sys {
				atomic.StoreUint64(&peakMemSys, stats.Sys)
			}
			time.Sleep(5 * time.Second)
		}
	}()
158
	// Import the chain
159
	start := time.Now()
160
	if err := utils.ImportChain(chain, ctx.Args().First()); err != nil {
161 162
		utils.Fatalf("Import error: %v", err)
	}
163
	fmt.Printf("Import done in %v.\n\n", time.Since(start))
164

165 166
	// Output pre-compaction stats mostly to see the import trashing
	db := chainDb.(*ethdb.LDBDatabase)
167

168 169 170 171 172 173 174
	stats, err := db.LDB().GetProperty("leveldb.stats")
	if err != nil {
		utils.Fatalf("Failed to read database stats: %v", err)
	}
	fmt.Println(stats)
	fmt.Printf("Trie cache misses:  %d\n", trie.CacheMisses())
	fmt.Printf("Trie cache unloads: %d\n\n", trie.CacheUnloads())
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189
	// Print the memory statistics used by the importing
	mem := new(runtime.MemStats)
	runtime.ReadMemStats(mem)

	fmt.Printf("Object memory: %.3f MB current, %.3f MB peak\n", float64(mem.Alloc)/1024/1024, float64(atomic.LoadUint64(&peakMemAlloc))/1024/1024)
	fmt.Printf("System memory: %.3f MB current, %.3f MB peak\n", float64(mem.Sys)/1024/1024, float64(atomic.LoadUint64(&peakMemSys))/1024/1024)
	fmt.Printf("Allocations:   %.3f million\n", float64(mem.Mallocs)/1000000)
	fmt.Printf("GC pause:      %v\n\n", time.Duration(mem.PauseTotalNs))

	// Compact the entire database to more accurately measure disk io and print the stats
	start = time.Now()
	fmt.Println("Compacting entire database...")
	if err = db.LDB().CompactRange(util.Range{}); err != nil {
		utils.Fatalf("Compaction failed: %v", err)
190
	}
191 192 193 194 195 196 197 198
	fmt.Printf("Compaction done in %v.\n\n", time.Since(start))

	stats, err = db.LDB().GetProperty("leveldb.stats")
	if err != nil {
		utils.Fatalf("Failed to read database stats: %v", err)
	}
	fmt.Println(stats)

199
	return nil
200 201
}

202
func exportChain(ctx *cli.Context) error {
Taylor Gerring's avatar
Taylor Gerring committed
203
	if len(ctx.Args()) < 1 {
204 205
		utils.Fatalf("This command requires an argument.")
	}
206 207
	stack := makeFullNode(ctx)
	chain, _ := utils.MakeChain(ctx, stack)
208
	start := time.Now()
209 210

	var err error
Taylor Gerring's avatar
Taylor Gerring committed
211
	fp := ctx.Args().First()
212
	if len(ctx.Args()) < 3 {
Taylor Gerring's avatar
Taylor Gerring committed
213
		err = utils.ExportChain(chain, fp)
214 215 216 217 218
	} 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 {
Taylor Gerring's avatar
Taylor Gerring committed
219
			utils.Fatalf("Export error in parsing parameters: block number not an integer\n")
220
		}
Taylor Gerring's avatar
Taylor Gerring committed
221 222 223 224
		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))
225 226 227
	}

	if err != nil {
228 229 230
		utils.Fatalf("Export error: %v\n", err)
	}
	fmt.Printf("Export done in %v", time.Since(start))
231
	return nil
232 233
}

234
func removeDB(ctx *cli.Context) error {
235
	stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
236
	dbdir := stack.ResolvePath(utils.ChainDbName(ctx))
237 238 239
	if !common.FileExist(dbdir) {
		fmt.Println(dbdir, "does not exist")
		return nil
240 241
	}

242 243 244 245 246 247 248 249 250
	fmt.Println(dbdir)
	confirm, err := console.Stdin.PromptConfirm("Remove this database?")
	switch {
	case err != nil:
		utils.Fatalf("%v", err)
	case !confirm:
		fmt.Println("Operation aborted")
	default:
		fmt.Println("Removing...")
251
		start := time.Now()
252
		os.RemoveAll(dbdir)
253 254
		fmt.Printf("Removed in %v\n", time.Since(start))
	}
255
	return nil
256 257
}

258
func upgradeDB(ctx *cli.Context) error {
259 260
	glog.Infoln("Upgrading blockchain database")

261 262
	stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
	chain, chainDb := utils.MakeChain(ctx, stack)
263
	bcVersion := core.GetBlockChainVersion(chainDb)
264 265 266 267 268 269 270 271
	if bcVersion == 0 {
		bcVersion = core.BlockChainVersion
	}

	// Export the current chain.
	filename := fmt.Sprintf("blockchain_%d_%s.chain", bcVersion, time.Now().Format("20060102_150405"))
	exportFile := filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
	if err := utils.ExportChain(chain, exportFile); err != nil {
272
		utils.Fatalf("Unable to export chain for reimport %s", err)
273
	}
274
	chainDb.Close()
275 276 277
	if dir := dbDirectory(chainDb); dir != "" {
		os.RemoveAll(dir)
	}
278 279

	// Import the chain file.
280
	chain, chainDb = utils.MakeChain(ctx, stack)
281
	core.WriteBlockChainVersion(chainDb, core.BlockChainVersion)
282
	err := utils.ImportChain(chain, exportFile)
283
	chainDb.Close()
284
	if err != nil {
285
		utils.Fatalf("Import error %v (a backup is made in %s, use the import command to import it)", err, exportFile)
286 287 288 289
	} else {
		os.Remove(exportFile)
		glog.Infoln("Import finished")
	}
290
	return nil
291 292
}

293 294 295 296 297 298 299 300
func dbDirectory(db ethdb.Database) string {
	ldb, ok := db.(*ethdb.LDBDatabase)
	if !ok {
		return ""
	}
	return ldb.Path()
}

301
func dump(ctx *cli.Context) error {
302 303
	stack := makeFullNode(ctx)
	chain, chainDb := utils.MakeChain(ctx, stack)
304 305 306
	for _, arg := range ctx.Args() {
		var block *types.Block
		if hashish(arg) {
307
			block = chain.GetBlockByHash(common.HexToHash(arg))
308 309 310 311 312 313 314 315
		} else {
			num, _ := strconv.Atoi(arg)
			block = chain.GetBlockByNumber(uint64(num))
		}
		if block == nil {
			fmt.Println("{}")
			utils.Fatalf("block not found")
		} else {
316 317 318 319
			state, err := state.New(block.Root(), chainDb)
			if err != nil {
				utils.Fatalf("could not create new state: %v", err)
			}
320 321 322
			fmt.Printf("%s\n", state.Dump())
		}
	}
323
	chainDb.Close()
324
	return nil
325 326 327 328 329 330 331 332
}

// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
	_, err := strconv.Atoi(x)
	return err != nil
}

333
func closeAll(dbs ...ethdb.Database) {
334 335 336 337
	for _, db := range dbs {
		db.Close()
	}
}