注:以下所描述的启动过程,是geth命令启动的方式
1. 主程序:
文件位置:cmd/geth/main.go
该文件是geth程序的主程序,其主函数如下:
func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
其中,最核心的代码是if条件中的
app.Run(os.Args)
app其实是通过文件:cmd/utils/flag.go中的NewApp方法生成的,而NewApp则是调用了"gopkg.in/urfave/cli.v1"模块,该模块以我的理解,就是用来创建基于命令行的程序的,我们不详述。
现在,我们只说到,我们基于一个模块创建一个app,而这个app是怎么与区块链的程序连接起来的呢?
我们回到cmd/geth/main.go文件,找到一个函数:
func geth(ctx *cli.Context) error { log.Warn(">>>>>>>>>>>>>>>>> Will start geth with context: ", "ctx", ctx) node := makeFullNode(ctx) log.Warn(">>>>>>>>>>>>>>>>> Full node created: ", "node", node) startNode(ctx, node) node.Wait() return nil }
我们可以发现,这个函数中创建了一个node,并启动了它。看起来,它才是真正的入口函数。那么,它是怎么与app绑定,并被app调用和执行的呢?
我们找到init方法,会发现其中有这么一行代码:
app.Action = geth
对,就是这句,将geth这个方法体,传给了app.Action属性,然后通过main()方法中的app.Run()方法,对geth方法进行了调用。
我们再从geth开始,继续往下走,先说makeFullNode
2. makeFullNode()
文件位置:cmd/geth/config.go
该方法,会生成一个*node.Node对象stack,并在第1步中的main.geth方法中返回。
然后main.geth方法调用startNode()方法来启动node
startNode(ctx, node)
同时,在config.go的makeFullNode方法,在生成了Node对象stack之后,会通过
utils.RegisterEthService(stack, &cfg.Eth)
还为stack注册一个Service,这个Service就是Ethereum Backend。
重点提示一点:node.Node结构体,通过一个成员变量serviceFuncs来保存一系列的构造函数,然后用这些构造函数去创建Service。其中,Ethereum Backend的构造函数是这样的:
文件位置:cmd/utils/flags.go
代码:
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 })
其中Register的参数中的func定义,就是一个构造函数,它生成了一个node.Service接口对象,其实就是一个Ethereum Backedn的对象。
而node.Node正是通过Register方法,不断的添加这些构造函数的。
关于怎么注册服务就简单说这些,下面继续重点讲启动的过程,也就是startNode方法。
3. startNode
文件位置:cmd/geth/main.go
该方法做了如下事情:
(1)启动node
utils.StartNode(stack)
(2)解锁一些指定的账户
(3)创建了一个长度16的钱包事件缓冲区,并将其传给账户管理器,让其被账户管理器管理
(4)连接node自身的rpc服务生成一个rpcclient,进而形成一个stateReader,通过goroutin提供给钱包程序处理事件时连接使用。注:前面的utils.StartNode会把rpc服务启动,所以此处可以连接。
(5)判断是否是挖矿节点,如果是,则获取node的Ethereum Backend(它是乙太坊的核心线程),然后通过
ethereum.StartMining启动挖矿线程。注:前面的utils.StartNode会把Ethereum Backend服务启动,所以此处可以获取到它。
今天的重点是第(1)步,其它4个部分不详述了。
4. utils.StartNode()
文件位置:cmd/utils/cmd.go
func StartNode(stack *node.Node) { if err := stack.Start(); err != nil { Fatalf("Error starting protocol stack: %v", err) } go func() { sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) <-sigc log.Info("Got interrupt, shutting down...") go stack.Stop() for i := 10; i > 0; i-- { <-sigc if i > 1 { log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) } } debug.Exit() // ensure trace and CPU profile data is flushed. debug.LoudPanic("boom") }() }
该方法中,核心的代码是第一行的 stack.Start()方法,至于下面的部分,则是通过监听特定的系统信息SIGINT和SIGTERM,来关闭node,这里就不详述了。我们继续从stack.Start()往下走。
5. Start()
文件位置:node/node.go
该方法做了如下几个事情:
(1)生成了P2P的服务对象running,以备后面启动
n.serverConfig = n.config.P2P n.serverConfig.PrivateKey = n.config.NodeKey() n.serverConfig.Name = n.config.NodeName() n.serverConfig.Logger = n.log if n.serverConfig.StaticNodes == nil { n.serverConfig.StaticNodes = n.config.StaticNodes() } if n.serverConfig.TrustedNodes == nil { n.serverConfig.TrustedNodes = n.config.TrustedNodes() } if n.serverConfig.NodeDatabase == "" { n.serverConfig.NodeDatabase = n.config.NodeDB() } running := &p2p.Server{Config: n.serverConfig}
(2)通过node的serviceFuncs所包含的构造函数,生成了一系列的Service。其实在我们的场景下,它就生成了一个Ethereum Backend。
services := make(map[reflect.Type]Service) for _, constructor := range n.serviceFuncs { // Create a new context for the particular service ctx := &ServiceContext{ config: n.config, services: make(map[reflect.Type]Service), EventMux: n.eventmux, AccountManager: n.accman, } for kind, s := range services { // copy needed for threaded access ctx.services[kind] = s } // Construct and save the service service, err := constructor(ctx) if err != nil { return err } kind := reflect.TypeOf(service) if _, exists := services[kind]; exists { return &DuplicateServiceError{Kind: kind} } services[kind] = service }
另外,还将这些Service所使用的协议,加入到了P2P服务对象running的协议变量Protocols中维护了起来。
// Gather the protocols and start the freshly assembled P2P server for _, service := range services { running.Protocols = append(running.Protocols, service.Protocols()...) }
(3)启动P2P服务running
if err := running.Start(); err != nil { return convertFileLockError(err) }
(4)启动(2)中生成的Service
// Start each of the services started := []reflect.Type{} for kind, service := range services { // Start the next service, stopping all previous upon failure if err := service.Start(running); err != nil { for _, kind := range started { services[kind].Stop() } running.Stop() return err } // Mark the service started for potential cleanup started = append(started, kind) }
(5)启动(2)中Service所包含的RPC服务。
if err := n.startRPC(services); err != nil { for _, service := range services { service.Stop() } running.Stop() return err }
好了,以上就是乙太坊node的整个启动过程。我从代码层面将其调用关系一层层串连了起来,给大家学习乙太坊代码提供一个参考。
在乙太坊进程中,真正工作的其实是Service,也就是Ethereum Backend。今天的这篇文章,我们简单提了一下它的启动过程。下一篇文章,我们就说一下它被创建和启动的详细过程。