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()开始挖矿。

近期评论