main.go 12.9 KB
Newer Older
obscuren's avatar
obscuren committed
1 2
/*
	This file is part of go-ethereum
Felix Lange's avatar
Felix Lange committed
3

obscuren's avatar
obscuren committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
	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/>.
*/
/**
 * @authors
 * 	Jeffrey Wilcke <i@jev.io>
 */
21 22 23
package main

import (
24
	"bufio"
obscuren's avatar
obscuren committed
25 26
	"fmt"
	"os"
obscuren's avatar
obscuren committed
27
	"runtime"
28
	"strconv"
obscuren's avatar
obscuren committed
29
	"time"
obscuren's avatar
obscuren committed
30

31
	"github.com/codegangsta/cli"
32
	"github.com/ethereum/ethash"
zelig's avatar
zelig committed
33
	"github.com/ethereum/go-ethereum/accounts"
obscuren's avatar
obscuren committed
34
	"github.com/ethereum/go-ethereum/cmd/utils"
35
	"github.com/ethereum/go-ethereum/common"
obscuren's avatar
obscuren committed
36
	"github.com/ethereum/go-ethereum/core/state"
37
	"github.com/ethereum/go-ethereum/core/types"
obscuren's avatar
obscuren committed
38
	"github.com/ethereum/go-ethereum/eth"
obscuren's avatar
obscuren committed
39
	"github.com/ethereum/go-ethereum/logger"
40
	"github.com/peterh/liner"
41 42
)

zelig's avatar
zelig committed
43 44
const (
	ClientIdentifier = "Ethereum(G)"
obscuren's avatar
obscuren committed
45
	Version          = "0.9.4"
zelig's avatar
zelig committed
46 47
)

48 49
var (
	clilogger = logger.NewLogger("CLI")
Felix Lange's avatar
Felix Lange committed
50
	app       = utils.NewApp(Version, "the go-ethereum command line interface")
51
)
52

53 54 55 56
func init() {
	app.Action = run
	app.HideVersion = true // we have a command to print the version
	app.Commands = []cli.Command{
57
		blocktestCmd,
58 59 60 61 62 63 64 65 66 67 68
		{
			Action: makedag,
			Name:   "makedag",
			Usage:  "generate ethash dag (for testing)",
			Description: `
The makedag command generates an ethash DAG in /tmp/dag.

This command exists to support the system testing project.
Regular users do not need to execute it.
`,
		},
69 70 71 72 73 74 75 76
		{
			Action: version,
			Name:   "version",
			Usage:  "print ethereum version numbers",
			Description: `
The output of this command is supposed to be machine-readable.
`,
		},
77 78 79 80 81 82 83 84 85
		{
			Action: accountList,
			Name:   "account",
			Usage:  "manage accounts",
			Subcommands: []cli.Command{
				{
					Action: accountList,
					Name:   "list",
					Usage:  "print account addresses",
zelig's avatar
zelig committed
86 87 88
					Description: `

`,
89 90 91 92 93
				},
				{
					Action: accountCreate,
					Name:   "new",
					Usage:  "create a new account",
zelig's avatar
zelig committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
					Description: `

    ethereum account new

Creates a new accountThe account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in future.
For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account new

					`,
				},
				{
					Action: accountImport,
					Name:   "import",
					Usage:  "import a private key into a new account",
					Description: `

    ethereum account import <keyfile>

Imports a private key from <keyfile> and creates a new account with the address derived from the key.
The keyfile is assumed to contain an unencrypted private key in canonical EC format.

The account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in future.
For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account import <keyfile>

					`,
				},
				{
					Action: accountExport,
					Name:   "export",
					Usage:  "export an account into key file",
					Description: `

    ethereum account export <address> <keyfile>

Exports the given account's private key into keyfile using the canonical EC format.
The account needs to be unlocked, if it is not the user is prompted for a passphrase to unlock it.
For non-interactive use, the password can be specified with the --unlock flag:

zelig's avatar
zelig committed
137
    ethereum --password <passwrdfile> account export <address> <keyfile>
zelig's avatar
zelig committed
138 139 140 141

Note:
Since you can directly copy your encrypted accounts to another ethereum instance, this import/export mechanism is not needed when you transfer an account between nodes.
					`,
142 143 144
				},
			},
		},
145 146 147 148 149 150 151 152 153 154
		{
			Action: dump,
			Name:   "dump",
			Usage:  `dump a specific block from storage`,
			Description: `
The arguments are interpreted as block numbers or hashes.
Use "ethereum dump 0" to dump the genesis block.
`,
		},
		{
zelig's avatar
zelig committed
155 156
			Action: console,
			Name:   "console",
157
			Usage:  `Ethereum Console: interactive JavaScript environment`,
zelig's avatar
zelig committed
158
			Description: `
159
Console is an interactive shell for the Ethereum JavaScript runtime environment which exposes a node admin interface as well as the DAPP JavaScript API.
zelig's avatar
zelig committed
160 161 162 163 164
See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console
`,
		},
		{
			Action: execJSFiles,
165
			Name:   "js",
zelig's avatar
zelig committed
166
			Usage:  `executes the given JavaScript files in the Ethereum Frontier JavaScript VM`,
167
			Description: `
168
The Ethereum JavaScript VM exposes a node admin interface as well as the DAPP JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console
169 170 171 172 173 174 175
`,
		},
		{
			Action: importchain,
			Name:   "import",
			Usage:  `import a blockchain file`,
		},
176 177 178 179 180
		{
			Action: exportchain,
			Name:   "export",
			Usage:  `export blockchain into file`,
		},
181 182
	}
	app.Flags = []cli.Flag{
183
		utils.UnlockedAccountFlag,
zelig's avatar
zelig committed
184
		utils.PasswordFileFlag,
185 186
		utils.BootnodesFlag,
		utils.DataDirFlag,
zelig's avatar
zelig committed
187
		utils.JSpathFlag,
188 189
		utils.ListenPortFlag,
		utils.LogFileFlag,
190
		utils.LogJSONFlag,
191 192 193 194 195 196 197 198 199 200
		utils.LogLevelFlag,
		utils.MaxPeersFlag,
		utils.MinerThreadsFlag,
		utils.MiningEnabledFlag,
		utils.NATFlag,
		utils.NodeKeyFileFlag,
		utils.NodeKeyHexFlag,
		utils.RPCEnabledFlag,
		utils.RPCListenAddrFlag,
		utils.RPCPortFlag,
201
		utils.UnencryptedKeysFlag,
202
		utils.VMDebugFlag,
zelig's avatar
zelig committed
203 204
		utils.ProtocolVersionFlag,
		utils.NetworkIdFlag,
205 206 207 208 209 210
	}

	// missing:
	// flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file")
	// flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0")
	// flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false")
211

212 213 214 215 216
	// potential subcommands:
	// flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)")
	// flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given")
	// flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key")
}
obscuren's avatar
obscuren committed
217

218 219 220 221 222 223 224 225
func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	defer logger.Flush()
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
zelig's avatar
zelig committed
226

227 228
func run(ctx *cli.Context) {
	fmt.Printf("Welcome to the FRONTIER\n")
229
	utils.HandleInterrupt()
230 231
	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
	ethereum, err := eth.New(cfg)
232
	if err != nil {
233 234 235
		utils.Fatalf("%v", err)
	}

236
	startEth(ctx, ethereum)
237
	// this blocks the thread
238
	ethereum.WaitForShutdown()
239
}
240

zelig's avatar
zelig committed
241
func console(ctx *cli.Context) {
242 243
	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
	ethereum, err := eth.New(cfg)
244
	if err != nil {
245 246 247
		utils.Fatalf("%v", err)
	}

248
	startEth(ctx, ethereum)
zelig's avatar
zelig committed
249 250 251 252 253 254 255 256 257 258 259 260
	repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name))
	repl.interactive()

	ethereum.Stop()
	ethereum.WaitForShutdown()
}

func execJSFiles(ctx *cli.Context) {
	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
	ethereum, err := eth.New(cfg)
	if err != nil {
		utils.Fatalf("%v", err)
obscuren's avatar
obscuren committed
261
	}
zelig's avatar
zelig committed
262 263 264 265 266 267 268

	startEth(ctx, ethereum)
	repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name))
	for _, file := range ctx.Args() {
		repl.exec(file)
	}

269 270
	ethereum.Stop()
	ethereum.WaitForShutdown()
271
}
obscuren's avatar
obscuren committed
272

zelig's avatar
zelig committed
273 274 275 276 277 278 279 280 281 282 283 284
func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (passphrase string) {
	if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) {
		var err error
		// Load startup keys. XXX we are going to need a different format
		// Attempt to unlock the account
		passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
		if len(passfile) == 0 {
			fmt.Println("Please enter a passphrase now.")
			auth, err := readPassword("Passphrase: ", true)
			if err != nil {
				utils.Fatalf("%v", err)
			}
285

zelig's avatar
zelig committed
286 287 288 289 290 291
			passphrase = auth

		} else {
			if passphrase, err = common.ReadAllFile(passfile); err != nil {
				utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
			}
292
		}
zelig's avatar
zelig committed
293 294

		err = am.Unlock(common.FromHex(account), passphrase)
295 296 297 298
		if err != nil {
			utils.Fatalf("Unlock account failed '%v'", err)
		}
	}
zelig's avatar
zelig committed
299 300 301 302 303 304 305 306 307
	return
}

func startEth(ctx *cli.Context, eth *eth.Ethereum) {
	utils.StartEthereum(eth)
	am := eth.AccountManager()

	account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
	if len(account) > 0 {
zelig's avatar
zelig committed
308 309 310
		if account == "coinbase" {
			account = ""
		}
zelig's avatar
zelig committed
311 312
		unlockAccount(ctx, am, account)
	}
313
	// Start auxiliary services if enabled.
314
	if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
315
		utils.StartRPC(eth, ctx)
316 317
	}
	if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
318
		eth.StartMining()
319 320
	}
}
obscuren's avatar
obscuren committed
321

322 323 324 325 326 327 328
func accountList(ctx *cli.Context) {
	am := utils.GetAccountManager(ctx)
	accts, err := am.Accounts()
	if err != nil {
		utils.Fatalf("Could not list accounts: %v", err)
	}
	for _, acct := range accts {
obscuren's avatar
obscuren committed
329
		fmt.Printf("Address: %x\n", acct)
330 331 332
	}
}

zelig's avatar
zelig committed
333
func getPassPhrase(ctx *cli.Context) (passphrase string) {
334
	if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) {
zelig's avatar
zelig committed
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
		passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
		if len(passfile) == 0 {
			fmt.Println("The new account will be encrypted with a passphrase.")
			fmt.Println("Please enter a passphrase now.")
			auth, err := readPassword("Passphrase: ", true)
			if err != nil {
				utils.Fatalf("%v", err)
			}
			confirm, err := readPassword("Repeat Passphrase: ", false)
			if err != nil {
				utils.Fatalf("%v", err)
			}
			if auth != confirm {
				utils.Fatalf("Passphrases did not match.")
			}
			passphrase = auth

		} else {
			var err error
			if passphrase, err = common.ReadAllFile(passfile); err != nil {
				utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
			}
357
		}
358
	}
zelig's avatar
zelig committed
359 360 361 362 363 364
	return
}

func accountCreate(ctx *cli.Context) {
	am := utils.GetAccountManager(ctx)
	passphrase := getPassPhrase(ctx)
365
	acct, err := am.NewAccount(passphrase)
366 367 368
	if err != nil {
		utils.Fatalf("Could not create the account: %v", err)
	}
zelig's avatar
zelig committed
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
	fmt.Printf("Address: %x\n", acct)
}

func accountImport(ctx *cli.Context) {
	keyfile := ctx.Args().First()
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as argument")
	}
	am := utils.GetAccountManager(ctx)
	passphrase := getPassPhrase(ctx)
	acct, err := am.Import(keyfile, passphrase)
	if err != nil {
		utils.Fatalf("Could not create the account: %v", err)
	}
	fmt.Printf("Address: %x\n", acct)
}

func accountExport(ctx *cli.Context) {
	account := ctx.Args().First()
	if len(account) == 0 {
		utils.Fatalf("account address must be given as first argument")
	}
	keyfile := ctx.Args().Get(1)
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as second argument")
	}
	am := utils.GetAccountManager(ctx)
	auth := unlockAccount(ctx, am, account)
	err := am.Export(keyfile, common.FromHex(account), auth)
	if err != nil {
		utils.Fatalf("Account export failed: %v", err)
	}
401 402
}

403 404 405 406
func importchain(ctx *cli.Context) {
	if len(ctx.Args()) != 1 {
		utils.Fatalf("This command requires an argument.")
	}
407
	chainmgr, _, _ := utils.GetChain(ctx)
408
	start := time.Now()
409
	err := utils.ImportChain(chainmgr, ctx.Args().First())
obscuren's avatar
obscuren committed
410
	if err != nil {
411
		utils.Fatalf("Import error: %v\n", err)
obscuren's avatar
obscuren committed
412
	}
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
	fmt.Printf("Import done in %v", time.Since(start))
	return
}

func exportchain(ctx *cli.Context) {
	if len(ctx.Args()) != 1 {
		utils.Fatalf("This command requires an argument.")
	}
	chainmgr, _, _ := utils.GetChain(ctx)
	start := time.Now()
	err := utils.ExportChain(chainmgr, ctx.Args().First())
	if err != nil {
		utils.Fatalf("Export error: %v\n", err)
	}
	fmt.Printf("Export done in %v", time.Since(start))
428 429
	return
}
obscuren's avatar
obscuren committed
430

431
func dump(ctx *cli.Context) {
432
	chainmgr, _, stateDb := utils.GetChain(ctx)
433
	for _, arg := range ctx.Args() {
obscuren's avatar
obscuren committed
434
		var block *types.Block
435
		if hashish(arg) {
obscuren's avatar
obscuren committed
436
			block = chainmgr.GetBlock(common.HexToHash(arg))
obscuren's avatar
obscuren committed
437
		} else {
438
			num, _ := strconv.Atoi(arg)
439
			block = chainmgr.GetBlockByNumber(uint64(num))
obscuren's avatar
obscuren committed
440 441 442
		}
		if block == nil {
			fmt.Println("{}")
443 444
			utils.Fatalf("block not found")
		} else {
445
			statedb := state.New(block.Root(), stateDb)
446 447
			fmt.Printf("%s\n", statedb.Dump())
			// fmt.Println(block)
obscuren's avatar
obscuren committed
448
		}
449
	}
450
}
451

452 453 454 455 456 457 458 459 460
func makedag(ctx *cli.Context) {
	chain, _, _ := utils.GetChain(ctx)
	pow := ethash.New(chain)
	fmt.Println("making cache")
	pow.UpdateCache(true)
	fmt.Println("making DAG")
	pow.UpdateDAG()
}

461
func version(c *cli.Context) {
zelig's avatar
zelig committed
462 463 464 465 466 467
	fmt.Printf(`%v
Version: %v
Protocol Version: %d
Network Id: %d
GO: %s
OS: %s
obscuren's avatar
obscuren committed
468 469
GOPATH=%s
GOROOT=%s
zelig's avatar
zelig committed
470
`, ClientIdentifier, Version, c.GlobalInt(utils.ProtocolVersionFlag.Name), c.GlobalInt(utils.NetworkIdFlag.Name), runtime.Version(), runtime.GOOS, os.Getenv("GOPATH"), runtime.GOROOT())
obscuren's avatar
obscuren committed
471
}
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

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

func readPassword(prompt string, warnTerm bool) (string, error) {
	if liner.TerminalSupported() {
		lr := liner.NewLiner()
		defer lr.Close()
		return lr.PasswordPrompt(prompt)
	}
	if warnTerm {
		fmt.Println("!! Unsupported terminal, password will be echoed.")
	}
	fmt.Print(prompt)
	input, err := bufio.NewReader(os.Stdin).ReadString('\n')
	fmt.Println()
	return input, err
}