内容简介:本章节主要通过分析源代码来了解以太坊的启动流程,本文基于以太坊的源码版本是go-ethereum-release-1.8。Geth的启动入口位置在go-ethereum/cmd/main.go的253行main函数。
点击 区块链技术培训课程 获取更多区块链技术学习资料。
一、前言
本章节主要通过分析源代码来了解以太坊的启动流程,本文基于以太坊的源码版本是go-ethereum-release-1.8。
二、启动入口
Geth的启动入口位置在go-ethereum/cmd/main.go的253行main函数。
func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
Geth的命令行处理使用了urfave/cli这个库,urfave/cli这个库抽象了flag、commnad等模块,用户只需要简单的配置,urfave/cli会帮我们完成参数的解析和关联,也能够自动生成帮助信息。
但是我们在上面的启动代码中并没有看到参数配置等操作,因为main函数其实不是Geth真正的入口,Geth首先是调用go-ethereum/cmd/main.go第169行的init函数,然后再调用main函数, 当然能够先调init函数再调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{ // See chaincmd.go: initCommand, importCommand, exportCommand, importPreimagesCommand, exportPreimagesCommand, copydbCommand, removedbCommand, dumpCommand, // See monitorcmd.go: monitorCommand, // See accountcmd.go: accountCommand, walletCommand, // See consolecmd.go: consoleCommand, attachCommand, javascriptCommand, // See misccmd.go: makecacheCommand, makedagCommand, versionCommand, bugCommand, licenseCommand, // See config.go dumpConfigCommand, } sort.Sort(cli.CommandsByName(app.Commands)) app.Flags = append(app.Flags, nodeFlags...) app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) app.Before = func(ctx *cli.Context) error { runtime.GOMAXPROCS(runtime.NumCPU()) logdir := "" if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs") } if err := debug.Setup(ctx, logdir); err != nil { return err } // Cap the cache allowance and tune the garbage collector var mem gosigar.Mem if err := mem.Get(); err == nil { allowance := int(mem.Total / 1024 / 1024 / 3) if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance { log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance)) } } // Ensure Go's GC ignores the database cache for trigger percentage cache := ctx.GlobalInt(utils.CacheFlag.Name) gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) godebug.SetGCPercent(int(gogc)) // Start metrics export if enabled utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) return nil } app.After = func(ctx *cli.Context) error { debug.Exit() console.Stdin.Close() // Resets terminal mode. return nil } }
init函数主要是做了一些初始化的工作,其中 app.Action = geth 是设置主入口函数,也就是说urfave/cli初始化完成后, 会调用main函数,main函数中再调用app的action函数, 从而调用geth来启动以太坊。
三、geth函数
geth函数比较简单,主要调用makeFullNode函数创建节点,调用startNode启动节点中的服务,最后调用node.Wait使节点进入等待状态。
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 }
3.1 makeFullNode函数
makeFullNode函数来创建一个节点,geth中node对象可以认为是以太坊网络中的一个节点,这个节点中会包含各种服务,比如网络服务、eth服务、DashBoard服务等等。然后向node中注册一个以太坊服务
func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) utils.RegisterEthService(stack, &cfg.Eth) if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { 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) } if ctx.GlobalIsSet(utils.WhisperRestrictConnectionBetweenLightClientsFlag.Name) { cfg.Shh.RestrictConnectionBetweenLightClients = true } utils.RegisterShhService(stack, &cfg.Shh) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) } return stack }
makeConfigNode函数创建各一个Node对象。
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ Eth: eth.DefaultConfig, Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), Dashboard: dashboard.DefaultConfig, } // Load config file. if file := ctx.GlobalString(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) if err != nil { utils.Fatalf("Failed to create the protocol stack: %v", err) } utils.SetEthConfig(ctx, stack, &cfg.Eth) if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } utils.SetShhConfig(ctx, stack, &cfg.Shh) utils.SetDashboardConfig(ctx, &cfg.Dashboard) return stack, cfg }
makeConfigNode主要有两个任务:
1. 根据命令行的配置来初始化cfg对象, 如果用户没有配置,则使用默认配置,后面以太坊中的各个模块会根据cfg的配置来执行相应的初始化。
2. 通过Node的默认配置来创建一个Node并返回node和cfg。
Node可以看做是以太坊的各个服务的容器, 一个服务能够注册到node当中,必须实现Service 接口。
type Service interface { // Protocols retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol // APIs retrieves the list of RPC descriptors the service provides APIs() []rpc.API // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. Start(server *p2p.Server) error // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. Stop() error }
这样Node可以不用关心容器中的具体是哪一个服务,只要统一调用他们的接口就行。他们功能如下:
Protocols() 返回service要启动的P2P 协议列表
APIs() 返回service提供的RPC接口
Start() 启动已经初始化的service
Stop() 停止service中所有的go程,并阻塞当前go程直到service中所有go程都终止
3.2 startNode 函数
我们重新回到cmd/geth/main.go的geth函数中分析startNode()函数。
startNode函数主要做4个任务:
1. 启动node中的服务,主要流程是node将之前注册的所有service交给p2p.Server, 然后启动p2p.Server对象,然后逐个启动每个Service。
2. 解锁钱包中的账号
3. 注册钱包事件
4. 启动辅助服务,比如RPC服务,如果配置支持启动挖矿,则执行启动挖矿。
func startNode(ctx *cli.Context, stack *node.Node) { debug.Memsize.Add("node", stack) // 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) go func() { // Create a chain state reader for self-derivation rpcClient, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } stateReader := ethclient.NewClient(rpcClient) // Open any wallets already attached for _, wallet := range stack.AccountManager().Wallets() { if err := wallet.Open(""); err != nil { log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) } } // Listen for wallet event till termination for event := range events { switch event.Kind { case accounts.WalletArrived: if err := event.Wallet.Open(""); err != nil { log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) } case accounts.WalletOpened: status, _ := event.Wallet.Status() log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) derivationPath := accounts.DefaultBaseDerivationPath if event.Wallet.URL().Scheme == "ledger" { derivationPath = accounts.DefaultLedgerBaseDerivationPath } event.Wallet.SelfDerive(derivationPath, stateReader) case accounts.WalletDropped: log.Info("Old wallet dropped", "url", event.Wallet.URL()) event.Wallet.Close() } } }() // Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { // Mining only makes sense if a full Ethereum node is running if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { utils.Fatalf("Ethereum service not running: %v", err) } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name) if ctx.IsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) } ethereum.TxPool().SetGasPrice(gasprice) threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name) if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name) } if err := ethereum.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } }
3.3 Node.Wait函数
Node.Wait函数主要是让geth的主go成进入阻塞状态,保持整个程序不退出,直到从channel中收到Stop消息, Stop消息一般是用户关闭以太坊客户端引起的。
func (n *Node) Wait() { n.lock.RLock() if n.server == nil { n.lock.RUnlock() return } stop := n.stop n.lock.RUnlock() <-stop }
四、总结
以太坊的启动过程其实就是创建一个Node对象, 接着向Node对象中添加各种服务,然后调用这些服务的Start方法启动他们。Node看起来就像一个容器,将各种模块扔进其中,然后将他们联系起来。
Node中包括P2P服务、RPC服务、以太坊服务等, 我们后面一节会着重介绍以太坊服务,以太坊服务会包含区块链相关的核心模块的初始化和启动。
-END-
本文完,更多资讯敬请关注微信公众号“区块链工程师”
来源:链块学院
本文由布洛克专栏作者发布,不代表布洛克观点,版权归作者所有
——TheEnd——
关注“布洛克科技”
以上所述就是小编给大家介绍的《以太坊源码分析(二):以太坊启动流程分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。