一、以太坊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程序启动后,代码的调用有两种情况:
- 如果是geth命令行启动,不带子命令,那么直接调用app.Action = geth()函数。
- 如果带了子命令启动如:./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网络部分启动流程图为: