首页 > geth, GO > geth以太坊源码分析-启动服务2

geth以太坊源码分析-启动服务2

2018年6月23日 发表评论 阅读评论 28632次阅读    

上一篇geth以太坊源码学习-启动服务1写到geth()入口函数,这里继续后面看看以太坊是怎么创建一个节点,并且启动服务的。
再看下geth函数,主要是节点创建函数makeFullNode 和启动函数 startNode , 后者会创建协程启动节点,然后进入等待状态。

func geth(ctx *cli.Context) error {
    //默认情况下,如果不带子命令参数,那么app.Action = geth, 也就会调用geth()函数 来启动以太坊
    //新建一个全节点服务,根据参数来
    node := makeFullNode(ctx)

    //启动节点
    startNode(ctx, node)
    node.Wait()
    return nil
}  

节点创建函数makeFullNode

makeFullNode 函数用创建一个以太坊节点,节点类型根据ctx参数传递的命令行指令来控制。生成node.Node一个结构,里面会有任务函数栈, 这里设置各个服务到serviceFuncs 里面,包括:轻节点,全节点, dashboard, shh, 以及状态stats服务。然后注册以太坊节点到服务列表里面。

func makeFullNode(ctx *cli.Context) *node.Node {
    //生成node.Node一个结构,里面会有任务函数栈, 这里设置各个服务到serviceFuncs 里面,                                         
    //包括:轻节点,全节点, dashboard, shh, 以及状态stats服务
    stack, cfg := makeConfigNode(ctx)

    //在stack上增加一个以太坊节点,其实就是new一个Ethereum 后加到后者的AddLesServer serviceFuncs 里面去
    //然后在stack.Run的时候会盗用这些service 
    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)
        }
        //whisper是以太坊的基于其P2P网络的点对点消息协议
        utils.RegisterShhService(stack, &cfg.Shh)
    }    

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

这里发现大量的utils.Register 函数,这也是以太坊节点进行服务注册发现,启动的一个很重要的方式,下面先介绍一下。

以太坊service服务挂载流程

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

func RegisterEthService(stack *node.Node, cfg *eth.Config) {
    //在stack上增加一个以太坊节点,其实就是new一个Ethereum 后加到后者的AddLesServer serviceFuncs 里面去
    var err error
    if cfg.SyncMode == downloader.LightSync {
        //只需要创建一个轻节点就返回
        err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
            //类型为LightEthereum
            return les.New(ctx, cfg)
        })
    } else {
        //到stack上增加一个serviceFuncs 函数
        err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
            //全节点类型为Ethereum
            fullNode, err := eth.New(ctx, cfg)
            if fullNode != nil && cfg.LightServ > 0 {
                //新建一个LesServer轻量级节点,设置到全节点上
                ls, _ := les.NewLesServer(fullNode, cfg)
                fullNode.AddLesServer(ls)
            }
            return fullNode, err
        })
    } 
}

stack.Register 是关键,传入匿名函数,函数代码很简单,就是创建一个类型为轻节点LightEthereum 或者 全节点类型为Ethereum的结构。

// Register injects a new service into the node's stack. The service created by
// the passed constructor must be unique in its type with regard to sibling ones.
func (n *Node) Register(constructor ServiceConstructor) error {
    //将函数加到本节点的serviceFuncs 里面
    n.lock.Lock()
    defer n.lock.Unlock()

    if n.server != nil {
        return ErrNodeRunning
    }
    n.serviceFuncs = append(n.serviceFuncs, constructor)
    return nil
}

也就是将匿名服务创建函数加入到serviceFuncs列表后面,那么,这些服务怎么启用的呢?得看Node.Start()了,后者在startNode里面会调用到。先来看一下。
函数调用路径为:

  1. startNode(ctx, node);
  2. utils.StartNode(stack);
  3. stack.Start() ;

stack.Start() 负责启动P2P服务,并且依次启动上面Register的各个serviceFuncs相关服务。

func (n *Node) Start() error {
    //启动节点
    n.lock.Lock()
    defer n.lock.Unlock()
    //加锁数据目录
    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)

接下来处理 serviceFuncs, 需要先为serviceFuncs分别调用他们的匿名创建函数,生成对应的service,比如类型为轻节点LightEthereum 或者 全节点类型为Ethereum的结构 返回,保存到services map里面。

    //遍历所有的serviceFuncs 服务,分别新建一个ServiceContext 结构
    for _, constructor := range n.serviceFuncs {
        // Create a new context for the particular service
        // 为每一次service启动新建一个临时上下文结构传递进去
        ctx := &ServiceContext{
            config:         n.config,
            //这个services 干什么的?似乎是记录一下之前的所有serviceFuncs 的kind,service,方便其他service使用
            services:       make(map[reflect.Type]Service), 
            EventMux:       n.eventmux,
            AccountManager: n.accman,
        }
        //重新拷贝一下services 变量的所有成员给ctx.services, 所以,这里只能拷贝之前的serviceFuncs, 之后的没办法了
        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
    }

然后 启动P2P服务,并且 启动所有刚才创建的服务,分别调用, 如果出错就stop之前所有的服务并返回错误。

for _, service := range services {//收集所有的这些服务的协议名称。
        running.Protocols = append(running.Protocols, service.Protocols()...)
    }
    //启动P2P服务
    if err := running.Start(); err != nil {
        return convertFileLockError(err)
    }
    // Start each of the services
    started := []reflect.Type{}
    //启动所有刚才创建的服务,分别调用, 如果出错就stop之前所有的服务并返回错误
    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接口, 记录当前节点拥有的服务列表到services上面, 设置节点停止的管道用于通信。

    //启动节点的所有RPC服务,开启监听端口,包括HTTPS, websocket接口
    // Lastly start the configured RPC interfaces
    if err := n.startRPC(services); err != nil {
        for _, service := range services {
            service.Stop()
        }
        running.Stop()
        return err
    }
    // Finish initializing the startup
    n.services = services //记录所有的服务
    n.server = running
    n.stop = make(chan struct{})

    return nil
}

大概了解了以太坊的服务注册流程和启动方法了,基本上就是在stack变量,Node类上用Register登记所有的服务service,提供一个匿名创建函数创建服务结构,最后在startNode()的时候会依次调用匿名函数,然后调用服务的Start()启动器,启动服务。

启动函数 startNode

回到startNode,函数开头调用utils.StartNode(stack)去启动以太坊的其他服务,包括P2P节点,然后自动解锁指定的账号,配置的, 这样非交互状态下方便使用。再创建协程,用RPC监听钱包创建事件。
最后如果指定了--mine 选项,就自动开始挖矿了。

func startNode(ctx *cli.Context, stack *node.Node) {
    // Start up the node itself
    utils.StartNode(stack)

    // Unlock any account specifically requested
    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)
        }
    }
    // Register wallet event handlers to open and auto-derive wallets
    events := make(chan accounts.WalletEvent, 16)
    stack.AccountManager().Subscribe(events)

接下来是挖矿的启动代码了,不过这里不详细介绍挖矿相关的逻辑,下回再讲。

    // Start auxiliary services if enabled
    //如果指定了--mine 选项,就自动开始挖矿
    if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
        // Mining only makes sense if a full Ethereum node is running
        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(&ethereum); err != nil {
            utils.Fatalf("Ethereum service not running: %v", err)
        }
        // Use a reduced number of threads if requested
        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)
            }
        }
        // Set the gas price to the limits from the CLI and start mining
        ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
        //开启挖矿,创建协程到后台处理
        if err := ethereum.StartMining(true); err != nil {
            utils.Fatalf("Failed to start mining: %v", err)
        }
    }
}

node中获取当前注册的以太坊节点,根据类型Ethereum来查询,得到节点后StartMining。
关于utils.StartNode,主要就是调用stack.Start来启动各项服务,上面已经讲过了。

基本上到这里,以太坊节点创建完成,并且也启动了各个服务,包括 Ethereum service等,也启动了P2P节点,如果配置了--mine 就自动调用ethereum.StartMining()开始挖矿。

Share
分类: geth, GO 标签: , , ,

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。