[以太坊源码学习] 乙太坊node的启动过程

注:以下所描述的启动过程,是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。今天的这篇文章,我们简单提了一下它的启动过程。下一篇文章,我们就说一下它被创建和启动的详细过程。

你可能感兴趣的:(区块链)