geth以太坊源码分析-启动服务2
上一篇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里面会调用到。先来看一下。
函数调用路径为:
- startNode(ctx, node);
- utils.StartNode(stack);
- 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()开始挖矿。
近期评论