geth以太坊源码分析-启动服务1
最近区块链这么火,出于好奇想看看源码实现,比较著名的就是比特币和以太坊了,前者是始祖,后者因为有智能合约的存在,所以大量不会写区块链底层的人可以利用以太坊实现自己的链,实际上,就是ICO, token了。
找了两者的官方源码:比特币 和 以太坊, 由于之前对C,C++源码看的多一些,所以这回趁这机会再了解一下go怎么实现以太坊的。
一、编译运行
这里只简单举个例子,想编译运行的Google一下就可以了。
首先下载源码: https://github.com/kulv2012/go-ethereum
编译: make
初始化一下创世区块:build/bin/geth --datadir ./datadir/ init conf/genesis.json
然后就可以最简单的方式运行,会自动开始挖矿: build/bin/geth --datadir ./datadir --mine
下面开始从开头了解一下以太坊geth是怎么启动服务的。
二、初始化服务
初始化全局变量app
geth程序的入口函数在cmd/geth/main.go 里面,包括main,以及初始化等。函数开头初始化程序的全局变量app, 这里有点绕的是geth使用了gopkg.in/urfave/cli.v1 这个包,来管理程序的启动,以及命令行解析,因此读起来不那么顺。下面先放上创建全局变量app的部分:
var (
// The app that holds all commands and flags.
//新建一个全局的app结构,用来管理程序启动,命令行配置等
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
// flags that configure the node
nodeFlags = []cli.Flag{
utils.IdentityFlag,
//----
}
//utils函数如下:
func NewApp(gitCommit, usage string) *cli.App {
//其实创建的是这个结构:vendor/gopkg.in/urfave/cli.v1/app.go
app := cli.NewApp()
app.Name = filepath.Base(os.Args[0])
app.Author = ""
//app.Authors = nil
app.Email = ""
app.Version = params.Version
if len(gitCommit) >= 8 {
app.Version += "-" + gitCommit[:8]
}
app.Usage = usage
return app
}
主要就是新建了一个cli.NewApp结构,代码在vendor/gopkg.in/urfave/cli.v1/app.go 里面,这个包是用来管理程序启动的,App包括 几个基本的接口: Command(), Run(), Setup(), 这些接口将来会在main里面被调用到。
func NewApp() *App {
return &App{
Name: filepath.Base(os.Args[0]),
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
初始化子命令,程序入口函数设置
go语言默认会先调用init()函数,如果有的话,然后再调用main,geth的init做了很重要的工作:设置程序的子命令集,以及程序入口函数,调用准备函数app.Before,以及负责扫尾的app.After().
对于各项Command, app会解析参数比如如果参数有console, 那么会由app类去调度,调用consoleCommand对应的处理函数。
func init() {
//先调用初始化函数,设置app的各个参数
// Initialize the CLI app and start Geth
//geth的处理函数会在app.HandleAction 里面会调用
app.Action = geth //默认的操作,就是启动一个节点 , 如果有其他命令行参数,会调用到下面的Commands 里面去
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2017 The go-ethereum Authors"
//设置各个命令的处理类/函数,比如consoleCommand 最后调用到 localConsole
//如果命令行参数里面有下面的指令,就会直接调用下面的Command.Run方法,而不调用默认的app.Action方法
app.Commands = []cli.Command{
// See chaincmd.go:
initCommand, //初始化创世块
importCommand,
exportCommand,
copydbCommand,
removedbCommand,
dumpCommand,
// See monitorcmd.go:
monitorCommand,
// See accountcmd.go:
accountCommand,
walletCommand,
// See consolecmd.go:
consoleCommand, //js命令行终端
attachCommand,
javascriptCommand,
// See misccmd.go:
makecacheCommand,
makedagCommand,
versionCommand,
bugCommand,
licenseCommand,
// See config.go
dumpConfigCommand,
}
sort.Sort(cli.CommandsByName(app.Commands))
再简单看一下命令处理函数的结构,其实挺简单,最重要的是Action成员了,这个函数最后会被调用,也就是对应的处理函数了,比如localConsole, initGenesis 等。
consoleCommand = cli.Command{
Action: utils.MigrateFlags(localConsole), //这个函数最后会被调用,也就是对应的处理函数了
Name: "console",
Usage: "Start an interactive JavaScript environment",
Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...),
Category: "CONSOLE COMMANDS",
Description: `---`,
}
接下来需要初始化各个命令行参数的解析方式,以及设置Before, After 函数, 这两函数简单可以理解为,在app.Run()里面,会先后调用他们,来初始化或者收尾程序的部分工作。具体代码后面在main之后分析。
app.Flags = append(app.Flags, nodeFlags...)
app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, consoleFlags...)
app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, whisperFlags...)
//before函数在app.Run的开始会先调用,也就是gopkg.in/urfave/cli.v1/app.go Run函数的前面
app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
return err
}
// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)
utils.SetupNetwork(ctx)
return nil
}
//after函数在最后调用,app.Run 里面会设置defer function
app.After = func(ctx *cli.Context) error {
debug.Exit()
console.Stdin.Close() // Resets terminal mode.
return nil
}
}
main入口
由于使用了gopkg.in/urfave/cli.v1 包,所以main函数非常简单,直接调用app.Run即可。实际上对于geth来说,app接下来实际上调用的函数有2种情况:
- 如果是geth命令行启动,不带子命令,那么直接调用app.Action = geth()函数;
- 如果带有子命令比如build/bin/geth console 启动,那么会调用对应命令的Command.Action, 对于console来说就是调用的 localConsole()函数;
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Run的具体逻辑在 gopkg.in/urfave/cli.v1/app.go 里面,这里不多说了,反正就是上面2种情况。下面来看看如果不带任何其他子命令参数,那么会调用geth()函数。
geth()命令行启动服务
默认情况下,如果不带子命令参数,那么app.Action = geth, 也就会调用geth()函数 来启动以太坊节点。
以太坊大部分子命令包括geth的逻辑,启动的时候都是先用makeFullNode()创建一个全节点的node节点结构。然后 startNode() 启动服务。
func geth(ctx *cli.Context) error {
//默认情况下,如果不带子命令参数,那么app.Action = geth, 也就会调用geth()函数 来启动以太坊
//新建一个全节点服务,根据参数来
node := makeFullNode(ctx)
//启动节点
startNode(ctx, node)
node.Wait()
return nil
}
创建以太坊节点makeFullNode()和启动以太坊节点startNode()这两部分放到后一篇(geth以太坊源码学习-启动服务2)继续 。

近期评论