cmd 模块包含了很多子模块,基本上每个子模块表示一个可执行的命令,其中最重要的是 geth 命令,它是以太坊的命令行客户端。
geth 命令是以太坊提供的一个强大的命令行工具,它是使用以太坊的入口。它包括了很多子命令,你可以通过 geth --help
获得更多帮助信息。其运行方法是:geth [选项] 命令 [命令选项][参数…]
。
以下是 geth 包含的子命令以及对应的简单描述。
子命令
描述
account
管理账户
attach
启动交互式JavaScript环境(连接到节点)
bug
给 github 源代码仓库提 issue
console
启动交互式 JavaScript 环境
copydb
从文件夹创建本地链
dump
取出一个特定的区块
dumpconfig
显示配置值
export
导出区块链到文件
import
导入一个区块链文件
init
启动并初始化一个新的创世纪块
js
执行指定的JavaScript文件(多个)
license
显示许可信息
makecache
生成ethash验证缓存(用于测试)
makedag
生成ethash 挖矿DAG(用于测试)
monitor
监控和可视化节点指标
removedb
删除区块链和状态数据库
version
打印版本号
wallet
管理Ethereum预售钱包
help
显示一个命令或帮助一个命令列表
本文将逐一分析 geth 模块的源码,了解 geth 命令的实现原理。需要注意的是,这里我们不会深入分析每一个模块,因为这些模块的实现实际上是以太坊每个功能模块的实现,在后续的文章我们会一一分析。这里只分析 geth 命令的实现。
将涉及到 cmd, node 目录。
geth 的命令行是通过 github.com/urfave/cli 这个库实现的,通过这个库,我们可以轻松定义命令行程序的子命令,命令选项,命令参数,描述信息等等,如果想要进一步了解,可以查看该库文档。
geth 模块的入口在 cmd/geth/main.go
中,它会调用 urfave/cli
的中 app 的 run
方法,而 app 在 init
函数中初始化,在 Golang 中,如果有 init
方法,那么会在 main
函数之前执行 init
函数,它用于程序执行前的初始化工作。在 geth 模块中,init()
函数定义了命令行的入口是 geth
,并且定义了 geth 的子命令、全局的命令选项、子命令的命令选项,按照 urfave/cli
的做法,不输入子命令会默认调用 geth,而 geth 方法其实就6行:
1 2 3 4 5 6 func geth(ctx *cli.Context) error { node := makeFullNode(ctx) startNode(ctx, node) node.Wait() return nil }
它会调用 makeFullNode
函数初始化一个全节点,该方法在 geth/config.go
中,接着通过 startNode
函数启动一个全节点,以阻塞的方式运行,等待着节点被终止。
我们先深入到 makeFullNode
函数中。
1 2 3 4 5 6 7 8 9 10 func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) utils.RegisterEthService(stack, &cfg.Eth) if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) } // whether enable whisper ... // whether register eth stats ... return stack }
核心的逻辑是首先通过配置文件和 flag 生成系统级的配置,然后将服务注入到节点。 先说 makeConfigNode
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { cfg := gethConfig{ Eth: eth.DefaultConfig, Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), Dashboard: dashboard.DefaultConfig, } if file := ctx.GlobalString(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) } utils.SetEthConfig(ctx, stack, &cfg.Eth) if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } utils.SetShhConfig(ctx, stack, &cfg.Shh) utils.SetDashboardConfig(ctx, &cfg.Dashboard) return stack, cfg }
makeConfigNode
会先载入默认配置,再载入配置文件中的配置,然后通过上下文的配置(在 cmd/geth/main.go
中的 init
方法中定义)进行设置。我们深入到 RegisterEthService
方法来查看服务是如何注入到节点中的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func RegisterEthService(stack *node.Node, cfg *eth.Config) { var err error if cfg.SyncMode == downloader.LightSync { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return les.New(ctx, cfg) }) } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { fullNode, err := eth.New(ctx, cfg) if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } return fullNode, err }) } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } }
RegisterEthService
的代码在 cmd/utils/flags.go
中,如果同步模式是轻量级同步模式,启动轻量级客户端,否则启动全节点,实际的注册方法是 stack.Register
。注入服务其实就是将新的服务注入到 node
对象的 serviceFuncs
数组中。这些内容将go-ethereum 源码笔记(node 模块) 描述。
接下来我们继续看 geth/main.go
的 startNode
函数,看看如何启动节点,这个方法在 cmd/geth/main.go
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 func startNode(ctx *cli.Context, stack *node.Node) { debug.Memsize.Add("node", stack) utils.StartNode(stack) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) passwords := utils.MakePasswordList(ctx) unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") for i, account := range unlocks { if trimmed := strings.TrimSpace(account); trimmed != "" { unlockAccount(ctx, ks, trimmed, i, passwords) } } events := make(chan accounts.WalletEvent, 16) stack.AccountManager().Subscribe(events) go func() { rpcClient, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } stateReader := ethclient.NewClient(rpcClient) for _, wallet := range stack.AccountManager().Wallets() { if err := wallet.Open(""); err != nil { log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) } } for event := range events { switch event.Kind { case accounts.WalletArrived: if err := event.Wallet.Open(""); err != nil { log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) } case accounts.WalletOpened: status, _ := event.Wallet.Status() log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) if event.Wallet.URL().Scheme == "ledger" { event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader) } else { event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader) } case accounts.WalletDropped: log.Info("Old wallet dropped", "url", event.Wallet.URL()) event.Wallet.Close() } } }() if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { utils.Fatalf("Ethereum service not running: %v", err) } if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 { type threaded interface { SetThreads(threads int) } if th, ok := ethereum.Engine().(threaded); ok { th.SetThreads(threads) } } ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name)) if err := ethereum.StartMining(true); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } }
startNode
方法启动节点,会开启所有已经注册的协议,解锁请求的账户,开启 RPC/IPC 接口,并开始挖矿。这里我们不再深入。
举两个例子。想要查阅某个命令或某些参数的帮助信息,可以使用:
查看各种命令,参数的提示信息。
如果想要启动一条私有链,可以使用下面的命令进行初始化:
1 $ geth --datadir "./" init genesis.json
其中 genesis.json 是创始区块的配置信息。
然后执行下面的命令,创建私有链:
1 $ geth --datadir "./" --nodiscover console 2>>geth.log
上述就是直接运行 geth,不输入其他子命令的情况。geth 还有很多子命令,这些子命令在 init()
的 app.Commands
赋值语句中可以看到,接下来会概述这些子命令。
chaincmd.go initCommand: geth init 这个命令会进行初始化,生成创始区块。对应调用的方法是 initGenesis
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func initGenesis(ctx *cli.Context) error { genesisPath := ctx.Args().First() if len(genesisPath) == 0 { utils.Fatalf("Must supply path to genesis JSON file") } file, err := os.Open(genesisPath) if err != nil { utils.Fatalf("Failed to read genesis file: %v", err) } defer file.Close() genesis := new(core.Genesis) if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } stack := makeFullNode(ctx) for _, name := range []string{"chaindata", "lightchaindata"} { chaindb, err := stack.OpenDatabase(name, 0, 0) if err != nil { utils.Fatalf("Failed to open database: %v", err) } _, hash, err := core.SetupGenesisBlock(chaindb, genesis) if err != nil { utils.Fatalf("Failed to write genesis block: %v", err) } log.Info("Successfully wrote genesis state", "database", name, "hash", hash) } return nil }
最终会调用 core/geth/chaincmd
的 SetupGenesisBlock
,这里具体的实现细节我们将在go-ethereum 源码笔记(core 模块-区块链操作) 介绍。
importCommand: geth import 导入一个区块链文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 func importChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() 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) } }() start := time.Now() if len(ctx.Args()) == 1 { if err := utils.ImportChain(chain, ctx.Args().First()); err != nil { log.Error("Import error", "err", err) } } else { for _, arg := range ctx.Args() { if err := utils.ImportChain(chain, arg); err != nil { log.Error("Import error", "file", arg, "err", err) } } } chain.Stop() fmt.Printf("Import done in %v.\n\n", time.Since(start)) db := chainDb.(*ethdb.LDBDatabase) stats, err := db.LDB().GetProperty("leveldb.stats") if err != nil { utils.Fatalf("Failed to read database stats: %v", err) } fmt.Println(stats) ioStats, err := db.LDB().GetProperty("leveldb.iostats") if err != nil { utils.Fatalf("Failed to read database iostats: %v", err) } fmt.Println(ioStats) fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) fmt.Printf("Trie cache unloads: %d\n\n", trie.CacheUnloads()) 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)) if ctx.GlobalIsSet(utils.NoCompactionFlag.Name) { return nil } start = time.Now() fmt.Println("Compacting entire database...") if err = db.LDB().CompactRange(util.Range{}); err != nil { utils.Fatalf("Compaction failed: %v", err) } 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) ioStats, err = db.LDB().GetProperty("leveldb.iostats") if err != nil { utils.Fatalf("Failed to read database iostats: %v", err) } fmt.Println(ioStats) return nil }
真正的逻辑在 utils/cmd.go
中的 ImportChain
。
exportCommand: geth export 导出一个区块链文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) chain, _ := utils.MakeChain(ctx, stack) start := time.Now() var err error fp := ctx.Args().First() if len(ctx.Args()) < 3 { err = utils.ExportChain(chain, fp) } else { 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 { 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)) } if err != nil { utils.Fatalf("Export error: %v\n", err) } fmt.Printf("Export done in %v\n", time.Since(start)) return nil }
导出区块链的真正逻辑在 utils/cmd.go
中的 ExportChain
里。将导出一个 gz 文件。
importPreimagesCommand: geth import-preimages 将一个 preimages 导入当前节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) start := time.Now() if err := utils.ImportPreimages(diskdb, ctx.Args().First()); err != nil { utils.Fatalf("Export error: %v\n", err) } fmt.Printf("Export done in %v\n", time.Since(start)) return nil }
exportPreimagesCommand: geth export-preimages 从当前节点导出一个 image
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) start := time.Now() if err := utils.ExportPreimages(diskdb, ctx.Args().First()); err != nil { utils.Fatalf("Export error: %v\n", err) } fmt.Printf("Export done in %v\n", time.Since(start)) return nil }
copydbCommand: geth copydb
复制一个本地区块文件到文件夹
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 func copyDb(ctx *cli.Context) error { if len(ctx.Args()) != 1 { utils.Fatalf("Source chaindata directory path argument missing") } stack := makeFullNode(ctx) chain, chainDb := utils.MakeChain(ctx, stack) syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode) dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil) db, err := ethdb.NewLDBDatabase(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name), 256) if err != nil { return err } hc, err := core.NewHeaderChain(db, chain.Config(), chain.Engine(), func() bool { return false }) if err != nil { return err } peer := downloader.NewFakePeer("local", db, hc, dl) if err = dl.RegisterPeer("local", 63, peer); err != nil { return err } start := time.Now() currentHeader := hc.CurrentHeader() if err = dl.Synchronise("local", currentHeader.Hash(), hc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()), syncmode); err != nil { return err } for dl.Synchronising() { time.Sleep(10 * time.Millisecond) } fmt.Printf("Database copy done in %v\n", time.Since(start)) start = time.Now() fmt.Println("Compacting entire database...") if err = chainDb.(*ethdb.LDBDatabase).LDB().CompactRange(util.Range{}); err != nil { utils.Fatalf("Compaction failed: %v", err) } fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) return nil }
在一个文件夹中创建一个本地区块链。有意思的是这个过程并不是直接复制过去的,而是通过 downloader
模块里的 NewFakePeer
创建一个虚拟对等节点,然后再进行数据同步完成的。
removedbCommand: geth removedb
在当前数据库中移除区块链。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func removeDB(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) for _, name := range []string{"chaindata", "lightchaindata"} { logger := log.New("database", name) dbdir := stack.ResolvePath(name) if !common.FileExist(dbdir) { logger.Info("Database doesn't exist, skipping", "path", dbdir) continue } fmt.Println(dbdir) confirm, err := console.Stdin.PromptConfirm("Remove this database?") switch { case err != nil: utils.Fatalf("%v", err) case !confirm: logger.Warn("Database deletion aborted") default: start := time.Now() os.RemoveAll(dbdir) logger.Info("Database successfully deleted", "elapsed", common.PrettyDuration(time.Since(start))) } } return nil }
删除数据库的过程倒是比较干脆,直接通过 os
模块移除这个文件夹。
dumpCommand: geth dump [<blockHash> | <blockNum>]...
dump 子命令可以移除一个或多个特定的区块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func dump(ctx *cli.Context) error { stack := makeFullNode(ctx) chain, chainDb := utils.MakeChain(ctx, stack) for _, arg := range ctx.Args() { var block *types.Block if hashish(arg) { block = chain.GetBlockByHash(common.HexToHash(arg)) } else { num, _ := strconv.Atoi(arg) block = chain.GetBlockByNumber(uint64(num)) } if block == nil { fmt.Println("{}") utils.Fatalf("block not found") } else { state, err := state.New(block.Root(), state.NewDatabase(chainDb)) if err != nil { utils.Fatalf("could not create new state: %v", err) } fmt.Printf("%s\n", state.Dump()) } } chainDb.Close() return nil }
先根据区块号获取区块,然后调用 state 的 Dump
移除即可,这部分的实现在之后go-ethereum 源码笔记(core 模块-区块链操作) 会有描述。
monitorcmd.go 这部分代码不是核心内容,只是粗略的看了一下。
monitorComand: geth monitor
监控,图像化节点 metrics 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 func monitor(ctx *cli.Context) error { var ( client *rpc.Client err error ) endpoint := ctx.String(monitorCommandAttachFlag.Name) if client, err = dialRPC(endpoint); err != nil { utils.Fatalf("Unable to attach to geth node: %v", err) } defer client.Close() metrics, err := retrieveMetrics(client) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } monitored := resolveMetrics(metrics, ctx.Args()) if len(monitored) == 0 { list := expandMetrics(metrics, "") sort.Strings(list) if len(list) > 0 { utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } else { utils.Fatalf("No metrics collected by geth (--%s).\n", utils.MetricsEnabledFlag.Name) } } sort.Strings(monitored) if cols := len(monitored) / ctx.Int(monitorCommandRowsFlag.Name); cols > 6 { utils.Fatalf("Requested metrics (%d) spans more that 6 columns:\n - %s", len(monitored), strings.Join(monitored, "\n - ")) } if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) } defer termui.Close() rows := len(monitored) if max := ctx.Int(monitorCommandRowsFlag.Name); rows > max { rows = max } cols := (len(monitored) + rows - 1) / rows for i := 0; i < rows; i++ { termui.Body.AddRows(termui.NewRow()) } footer := termui.NewPar("") footer.Block.Border = true footer.Height = 3 charts := make([]*termui.LineChart, len(monitored)) units := make([]int, len(monitored)) data := make([][]float64, len(monitored)) for i := 0; i < len(monitored); i++ { charts[i] = createChart((termui.TermHeight() - footer.Height) / rows) row := termui.Body.Rows[i%rows] row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) refreshCharts(client, monitored, data, units, charts, ctx, footer) termui.Body.Align() termui.Render(termui.Body) termui.Handle("/sys/kbd/C-c", func(termui.Event) { termui.StopLoop() }) termui.Handle("/sys/wnd/resize", func(termui.Event) { termui.Body.Width = termui.TermWidth() for _, chart := range charts { chart.Height = (termui.TermHeight() - footer.Height) / rows } termui.Body.Align() termui.Render(termui.Body) }) go func() { tick := time.NewTicker(time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second) for range tick.C { if refreshCharts(client, monitored, data, units, charts, ctx, footer) { termui.Body.Align() } termui.Render(termui.Body) } }() termui.Loop() return nil }
accountcmd.go accountCommand 管理账户,这部分的实现在后续的博客go-ethereum 源码笔记(accounts, transaction 模块) 有描述。
list: geth account list
1 2 3 4 5 6 7 8 9 10 11 func accountList(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) var index int for _, wallet := range stack.AccountManager().Wallets() { for _, account := range wallet.Accounts() { fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) index++ } } return nil }
通过调用 accounts/manager.go
的 Wallets
拿到所有账户。
new: geth account new
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func accountCreate(ctx *cli.Context) error { cfg := gethConfig{Node: defaultNodeConfig()} if file := ctx.GlobalString(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } utils.SetNodeConfig(ctx, &cfg.Node) scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() if err != nil { utils.Fatalf("Failed to read configuration: %v", err) } password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) if err != nil { utils.Fatalf("Failed to create account: %v", err) } fmt.Printf("Address: {%x}\n", address) return nil }
创建一个账户,成功后输出地址。通过 accounts
模块实现。
update:geth account update <address>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func accountUpdate(ctx *cli.Context) error { if len(ctx.Args()) == 0 { utils.Fatalf("No accounts specified to update") } stack, _ := makeConfigNode(ctx) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) for _, addr := range ctx.Args() { account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) if err := ks.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } } return nil }
先通过 AccountManager
拿到 keystore,然后调用 Update
更新密码
import: geth account import <keyfile>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func accountImport(ctx *cli.Context) error { keyfile := ctx.Args().First() if len(keyfile) == 0 { utils.Fatalf("keyfile must be given as argument") } key, err := crypto.LoadECDSA(keyfile) if err != nil { utils.Fatalf("Failed to load the private key: %v", err) } stack, _ := makeConfigNode(ctx) passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportECDSA(key, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } fmt.Printf("Address: {%x}\n", acct.Address) return nil }
先通过 AccountManager
拿到 keystore,调用 ImportPreSaleKey
导入账户。
walletCommand: geth wallet import /path/to/my/presale.wallet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func importWallet(ctx *cli.Context) error { keyfile := ctx.Args().First() if len(keyfile) == 0 { utils.Fatalf("keyfile must be given as argument") } keyJSON, err := ioutil.ReadFile(keyfile) if err != nil { utils.Fatalf("Could not read wallet file: %v", err) } stack, _ := makeConfigNode(ctx) passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) if err != nil { utils.Fatalf("%v", err) } fmt.Printf("Address: {%x}\n", acct.Address) return nil }
通过 AccountManager
管理以太坊预售钱包。
consolecmd.go consoleCommand: geth console
启动一个 Javascript 交互式环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func localConsole(ctx *cli.Context) error { node := makeFullNode(ctx) startNode(ctx, node) defer node.Stop() client, err := node.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), Client: client, Preload: utils.MakeConsolePreloads(ctx), } console, err := console.New(config) if err != nil { utils.Fatalf("Failed to start the JavaScript console: %v", err) } defer console.Stop(false) if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { console.Evaluate(script) return nil } console.Welcome() console.Interactive() return nil }
启动本地的一个交互式 Javascript 环境,功能是通过 console
模块提供的,而 console
模块是对 robertkrimen/otto 的一个封装。otto 是一个 Golang 实现的 Javascript 解释器,可以实现在 Golang 中执行 Javascript,并且可以让在虚拟机里的 Javascript 调用 Golang 函数,实现 Golang 和 Javascript 的相互操作。
attachCommand 启动一个 JS 交互式环境(连接到节点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func remoteConsole(ctx *cli.Context) error { endpoint := ctx.Args().First() if endpoint == "" { path := node.DefaultDataDir() if ctx.GlobalIsSet(utils.DataDirFlag.Name) { path = ctx.GlobalString(utils.DataDirFlag.Name) } if path != "" { if ctx.GlobalBool(utils.TestnetFlag.Name) { path = filepath.Join(path, "testnet") } else if ctx.GlobalBool(utils.RinkebyFlag.Name) { path = filepath.Join(path, "rinkeby") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) } client, err := dialRPC(endpoint) if err != nil { utils.Fatalf("Unable to attach to remote geth: %v", err) } config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), Client: client, Preload: utils.MakeConsolePreloads(ctx), } console, err := console.New(config) if err != nil { utils.Fatalf("Failed to start the JavaScript console: %v", err) } defer console.Stop(false) if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { console.Evaluate(script) return nil } console.Welcome() console.Interactive() return nil }
通过指定 endpoint 的方式,连接到某个节点的交互式 Javascript 环境。
javascriptCommand: geth js <jsfile> [jsfile...]
执行 Javascript 文件中的命令(可以为多个文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 func ephemeralConsole(ctx *cli.Context) error { node := makeFullNode(ctx) startNode(ctx, node) defer node.Stop() client, err := node.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), Client: client, Preload: utils.MakeConsolePreloads(ctx), } console, err := console.New(config) if err != nil { utils.Fatalf("Failed to start the JavaScript console: %v", err) } defer console.Stop(false) for _, file := range ctx.Args() { if err = console.Execute(file); err != nil { utils.Fatalf("Failed to execute %s: %v", file, err) } } abort := make(chan os.Signal, 1) signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) go func() { <-abort os.Exit(0) }() console.Stop(true) return nil }
通过遍历调用传输的文件路径,执行 console.Execute
,执行 js 命令。
misccmd.go makecacheCommand: geth makecache <block number> <outputdir>
1 2 3 4 5 6 7 8 9 10 11 12 13 func makecache(ctx *cli.Context) error { args := ctx.Args() if len(args) != 2 { utils.Fatalf(`Usage: geth makecache <block number> <outputdir>`) } block, err := strconv.ParseUint(args[0], 0, 64) if err != nil { utils.Fatalf("Invalid block number: %v", err) } ethash.MakeCache(block, args[1]) return nil }
生成 ethash 的验证缓存。这部分内容将在go-ethereum 源码笔记(miner,consensus 模块) 描述。
makedagCommand: geth makedag <block number> <outputdir>
1 2 3 4 5 6 7 8 9 10 11 12 13 func makedag(ctx *cli.Context) error { args := ctx.Args() if len(args) != 2 { utils.Fatalf(`Usage: geth makedag <block number> <outputdir>`) } block, err := strconv.ParseUint(args[0], 0, 64) if err != nil { utils.Fatalf("Invalid block number: %v", err) } ethash.MakeDataset(block, args[1]) return nil }
通过调用 ethash 的 MakeDataset
,生成挖矿需要的 DAG 数据集。
versionCommand: geth version
输出版本号。
bugCommand 给 https://github.com/ethereum/go-ethereum/issues/new
这个 url 拼接参数,给源代码仓库提一个 issue
licenseCommand: geth license
输出 License 信息。
config.go dumpConfigCommand: geth dumpconfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func dumpConfig(ctx *cli.Context) error { _, cfg := makeConfigNode(ctx) comment := "" if cfg.Eth.Genesis != nil { cfg.Eth.Genesis = nil comment += "# Note: this config doesn't contain the genesis block.\n\n" } out, err := tomlSettings.Marshal(&cfg) if err != nil { return err } io.WriteString(os.Stdout, comment) os.Stdout.Write(out) return nil }
makeConfigNode
前面已经提过,这个方法用来获取当前配置信息,dumpConfig
函数通过 makeConfigNode
获取配置,然后将其输出在屏幕。