以太坊p2p网络(三):以太坊p2p网络启动源码分析

时间:2024-03-18 15:35:36

一、以太坊geth入口函数调用

geth程序的入口函数在go-ethereum/cmd/geth/main.go 里面,主要包括main函数,以及初始化等操作。

源码位置: go-ethereum/cmd/geth/main.go

func init() {
	// Initialize the CLI app and start Geth
	app.Action = geth
	app.HideVersion = true // we have a command to print the version
	app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
	app.Commands = []cli.Command{
	...
	}
	func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	
}

由于以太坊中的使用了 gopkg.in/urfave/cli.v1 包(后期再详细讲解以太坊中使用的各种包的使用),此包对于命令行参数的处理使得代码非常简洁,程序只需要调用 Command.Run() 即可启动。geth程序启动后,代码的调用有两种情况:

  1. 如果是geth命令行启动,不带子命令,那么直接调用app.Action = geth()函数。
  2. 如果带了子命令启动如:./geth init,则会调用对应的Action函数进行启动。

默认情况下,不带参数来启动以太坊节点,则会调用geth()函数:

源码位置: go-ethereum/cmd/geth/main.go

func geth(ctx *cli.Context) error {

   //命令行参数检查
	if args := ctx.Args(); len(args) > 0 {
		return fmt.Errorf("invalid command: %q", args[0])
	}
	
	//根据参数来新建一个全节点服务
	node := makeFullNode(ctx)
     
     //启动节点
	startNode(ctx, node)
	
	//等待节点
	node.Wait()
	return nil
}

二、节点创建

makeFullNode()函数 传入的参数是程序启动时的命令行参数,根据命令行参数来判断创建的节点类型新建节点stack,设置各种服务参数。函数将返回一个节点实例,将各种服务包括:Eth、DashBoard、Shh、EthStats服务注册stack.serviceFuncs ()中保存起来,也就是将这种服务的new Server保存到stack.serviceFuncs ()中,等到stack.Run()的时候开始执行new各种server操作,new出各种实例。


func makeFullNode(ctx *cli.Context) *node.Node {
	
	//生成node.Node一个结构
	stack, cfg := makeConfigNode(ctx)
	
	//注册eth服务,也就是new一个eth服务,将eth服务保存到节点stack的 serviceFuncs 中去
	//在stack.Run()的时候会调用这些service new出对象实例
	utils.RegisterEthService(stack, &cfg.Eth)

	if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
	
		//注册dashboard仪表盘服务,Dashboard会开启端口监听
		utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
	}
	// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
	shhEnabled := enableWhisper(ctx)
	shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
	if shhEnabled || shhAutoEnabled {
		if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
			cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
		}
		if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
			cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
		}
		//注册shh服务
		utils.RegisterShhService(stack, &cfg.Shh)
	}

	// Add the Ethereum Stats daemon if requested.
	if cfg.Ethstats.URL != "" {
		//注册EthStats服务
		utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
	}
	return stack
}

三、以太坊service服务的注册

节点的创建过程中使用了utils.Register函数,这是各种服务的注册,以太坊中服务的注册包括:RegisterShhService, RegisterEthStatsService,RegisterEthService,RegisterDashboardService。以RegisterEthService代码为例,RegisterEthService函数用来注册一个以太坊轻节点或者全节点,供后面启动,从下面代码可以看到,根据配置不同启动不同的节点,stack其实就是一个节点,以太坊的节点就是一个启动的geth程序。
在stack上增加一个以太坊节点,其实就是new一个Ethereum 后加到后者的 serviceFuncs 里面去。

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) {
		 //匿名函数中New出一个eth服务
			fullNode, err := eth.New(ctx, cfg)
			if fullNode != nil && cfg.LightServ > 0 {
			// //新建一个LesServer轻量级节点,设置到全节点上
				ls, _ := les.NewLesServer(fullNode, cfg)
				fullNode.AddLesServer(ls)
			}
			return fullNode, err
		})
	}
	if err != nil {
		Fatalf("Failed to register the Ethereum service: %v", err)
	}
}

stack.Register 函数传入匿名函数,创建一个类型为轻节点LightEthereum 或者 全节点类型为Ethereum的结构。

func (n *Node) Register(constructor ServiceConstructor) error {
	n.lock.Lock()
	defer n.lock.Unlock()

	if n.server != nil {
		return ErrNodeRunning
	}
	//将出入的函数添加到节点的serviceFuncs中
	n.serviceFuncs = append(n.serviceFuncs, constructor)
	return nil
}

四、p2p2网络的启动

通过geth函数里面的startNode(ctx, node)函数来启动p2p网络和各种服务。startNode(ctx, node)函数首先调用节点启动函数utils.StartNode(stack),再调用stack.Start()函数。

func (n *Node) Start() error {
	n.lock.Lock()
	defer n.lock.Unlock()

	// 判断节点是否已经启动
	if n.server != nil {
		return ErrNodeRunning
	}
	if err := n.openDataDir(); err != nil {
		return err
	}

	...
	//新建一个p2p服务
	running := &p2p.Server{Config: n.serverConfig}
	n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)

	// Otherwise copy and specialize the P2P configuration
	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
		}
		// 调用注册进来的各种服务,也就是调用new各种服务创建各种服务对象
		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
	}
	
	//收集创建各种服务的子协议
	for _, service := range services {
		running.Protocols = append(running.Protocols, service.Protocols()...)
	}
	
	//启动p2p服务
	if err := running.Start(); err != nil {
		return convertFileLockError(err)
	}
	// 调用注册的各种服务的Start()函数
	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)
	}
	// 动节点的所有RPC服务,开启监听端口,包括HTTPS, websocket接口
	if err := n.startRPC(services); err != nil {
		for _, service := range services {
			service.Stop()
		}
		running.Stop()
		return err
	}
	n.services = services
	n.server = running
	n.stop = make(chan struct{})
	
	return nil
}

则Geth程序p2p网络部分启动流程图为:
以太坊p2p网络(三):以太坊p2p网络启动源码分析