以太坊源码阅读6——geth启动流程

以太坊源码阅读6——geth启动流程,第1张

以太坊源码阅读6——geth启动流程 介绍

geth是我们的go-ethereum最主要的一个命令行工具。 也是我们的各种网络的接入点(主网络main-net 测试网络test-net 和私有网络)。支持运行在全节点模式或者轻量级节点模式。 其他程序可以通过它暴露的JSON RPC调用来访问以太坊网络的功能。如果什么命令都不输入直接运行geth。 就会默认启动一个全节点模式的节点。 连接到主网络。

init

在正式启动之前会先进行调用init函数初始化

func init() {
   // Initialize the CLI app and start Geth
   //初始化客户端app和并启动Geth
   //Action字段表示如果用户没有输入其他的子命令的情况下,会调用这个字段指向的函数。
   app.Action = geth
   app.HideVersion = true // we have a command to print the version
   app.Copyright = "Copyright 2013-2022 The go-ethereum Authors"
   //Commands 是所有支持的子命令
   app.Commands = []cli.Command{
      // See chaincmd.go:
      initCommand,
      importCommand,
      exportCommand,
      importPreimagesCommand,
      exportPreimagesCommand,
      removedbCommand,
      dumpCommand,
      dumpGenesisCommand,
      // See accountcmd.go:
      accountCommand,
      walletCommand,
      // See consolecmd.go:
      consoleCommand,
      attachCommand,
      javascriptCommand,
      // See misccmd.go:
      makecacheCommand,
      makedagCommand,
      versionCommand,
      versionCheckCommand,
      licenseCommand,
      // See config.go
      dumpConfigCommand,
      // see dbcmd.go
      dbCommand,
      // See cmd/utils/flags_legacy.go
      utils.ShowDeprecated,
      // See snapshot.go
      snapshotCommand,
   }
   sort.Sort(cli.CommandsByName(app.Commands))
   //所有能够解析的Options
   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, metricsFlags...)

   app.Before = func(ctx *cli.Context) error {
      return debug.Setup(ctx)
   }
   app.After = func(ctx *cli.Context) error {
      debug.Exit()
      prompt.Stdin.Close() // Resets terminal mode.
      return nil
   }
}

如果没有运行特殊的子命令,Geth就是进入系统的主要入口点。它根据命令行参数创建一个默认节点,并以阻塞模式运行它,等待它被关闭。

func geth(ctx *cli.Context) error {
	if args := ctx.Args(); len(args) > 0 {
		return fmt.Errorf("invalid command: %q", args[0])
	}

	prepare(ctx)
	stack, backend := makeFullNode(ctx)
	defer stack.Close()

	startNode(ctx, stack, backend, false)
	stack.Wait()
	return nil
}

准备 *** 作内存缓存量和设置度量系统(这里翻译了注释,应该是指设置什么区块链网络,主网、测试网一类的)。这个函数应该在启动dev p2p堆栈之前调用。

func prepare(ctx *cli.Context) {
	// If we're running a known preset, log it for convenience.
	// 确定启动在哪个网络上并记录
	switch {
	case ctx.GlobalIsSet(utils.RopstenFlag.Name):
		log.Info("Starting Geth on Ropsten testnet...")

	case ctx.GlobalIsSet(utils.SepoliaFlag.Name):
		log.Info("Starting Geth on Sepolia testnet...")

	case ctx.GlobalIsSet(utils.RinkebyFlag.Name):
		log.Info("Starting Geth on Rinkeby testnet...")

	case ctx.GlobalIsSet(utils.GoerliFlag.Name):
		log.Info("Starting Geth on Görli testnet...")

	case ctx.GlobalIsSet(utils.DeveloperFlag.Name):
		log.Info("Starting Geth in ephemeral dev mode...")

	case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name):
		log.Info("Starting Geth on Ethereum mainnet...")
	}
	// If we're a full node on mainnet without --cache specified, bump default cache allowance
	// 如果我们是主网上的一个完整节点,没有指定——cache,就取消默认的缓存允许
	if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
		// Make sure we're not on any supported preconfigured testnet either
		if !ctx.GlobalIsSet(utils.RopstenFlag.Name) &&
			!ctx.GlobalIsSet(utils.SepoliaFlag.Name) &&
			!ctx.GlobalIsSet(utils.RinkebyFlag.Name) &&
			!ctx.GlobalIsSet(utils.GoerliFlag.Name) &&
			!ctx.GlobalIsSet(utils.DeveloperFlag.Name) {
			// Nope, we're really on mainnet. Bump that cache up!
			log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096)
			ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))
		}
	}
	// If we're running a light client on any network, drop the cache to some meaningfully low amount
	//如果我们在任何网络上运行一个轻客户端,将缓存降低到一个有意义的低数量
	if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) {
		log.Info("Dropping default light client cache", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 128)
		ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128))
	}

	// Start metrics export if enabled
	utils.SetupMetrics(ctx)

	// Start system runtime metrics collection
	go metrics.CollectProcessMetrics(3 * time.Second)
}

在设置完内存和网络后,调用了makeFullNode,创建全节点

//makeFullNode加载geth配置并创建以太坊后端。
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
	// 根据命令行参数和一些特殊的配置来创建一个node
	stack, cfg := makeConfigNode(ctx)
	if ctx.GlobalIsSet(utils.OverrideArrowGlacierFlag.Name) {
		cfg.Eth.OverrideArrowGlacier = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideArrowGlacierFlag.Name))
	}
	if ctx.GlobalIsSet(utils.OverrideTerminalTotalDifficulty.Name) {
		cfg.Eth.OverrideTerminalTotalDifficulty = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideTerminalTotalDifficulty.Name))
	}
	// 把eth的服务注册到这个节点上面。 eth服务是以太坊的主要的服务。 是以太坊功能的提供者。
	backend, _ := utils.RegisterEthService(stack, &cfg.Eth)

	// Configure GraphQL if requested
	if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
		utils.RegisterGraphQLService(stack, backend, cfg.Node)
	}
	// Add the Ethereum Stats daemon if requested.
	if cfg.Ethstats.URL != "" {
		utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
	}
	return stack, backend
}

makeConfigNode。 这个函数主要是通过配置文件和flag来生成整个系统的运行配置。

//makeConfigNode加载geth配置并创建一个空节点实例。
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
	// Load defaults.
	// 默认配置
	cfg := gethConfig{
		Eth:     ethconfig.Defaults,
		Node:    defaultNodeConfig(),
		Metrics: metrics.DefaultConfig,
	}

	// Load config file.
	// 文件配置
	if file := ctx.GlobalString(configFileFlag.Name); file != "" {
		if err := loadConfig(file, &cfg); err != nil {
			utils.Fatalf("%v", err)
		}
	}

	// Apply flags.
	// 应用ctx的配置
	utils.SetNodeConfig(ctx, &cfg.Node)
	stack, err := node.New(&cfg.Node)
	if err != nil {
		utils.Fatalf("Failed to create the protocol stack: %v", err)
	}
	// Node doesn't by default populate account manager backends
	if err := setAccountManagerBackends(stack); err != nil {
		utils.Fatalf("Failed to set account manager backends: %v", err)
	}

	utils.SetEthConfig(ctx, stack, &cfg.Eth)
	if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
		cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
	}
	applyMetricConfig(ctx, &cfg)

	return stack, cfg
}

RegisterEthService 将一个以太坊客户端添加到栈中。

// 第二个返回值是完整的节点实例,如果节点作为轻客户端运行,则该实例可能为nil。
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
	// 如果同步模式是轻量级的同步模式。 那么启动轻量级的客户端。
	if cfg.SyncMode == downloader.LightSync {
		backend, err := les.New(stack, cfg)
		if err != nil {
			Fatalf("Failed to register the Ethereum service: %v", err)
		}
		stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
		if backend.BlockChain().Config().TerminalTotalDifficulty != nil {
			if err := lescatalyst.Register(stack, backend); err != nil {
				Fatalf("Failed to register the catalyst service: %v", err)
			}
		}
		return backend.ApiBackend, nil
	}
	// 否则会启动全节点
	// 这里的New方法会创建一个新的以太坊对象(包括公共以太坊对象的初始化)
	backend, err := eth.New(stack, cfg)
	if err != nil {
		Fatalf("Failed to register the Ethereum service: %v", err)
	}
	// 默认LightServ的大小是0 也就是不会启动LesServer
	if cfg.LightServ > 0 {
		//LesServer是给轻量级节点提供服务的。
		_, err := les.NewLesServer(stack, backend, cfg)
		if err != nil {
			Fatalf("Failed to create the LES server: %v", err)
		}
	}
	if backend.BlockChain().Config().TerminalTotalDifficulty != nil {
		if err := ethcatalyst.Register(stack, backend); err != nil {
			Fatalf("Failed to register the catalyst service: %v", err)
		}
	}
	stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
	return backend.APIBackend, backend
}

startNode

//startNode启动系统节点和所有注册的协议,然后解锁所有请求的帐户,并启动RPC/IPC接口和矿机。
//IPC: Inter-Process Communication,进程间通信
func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) {
	debug.Memsize.Add("node", stack)

	// Start up the node itself
	// 启动节点本身
	utils.StartNode(ctx, stack, isConsole)

	// Unlock any account specifically requested
	//解锁特定请求的帐户
	unlockAccounts(ctx, stack)

	// Register wallet event handlers to open and auto-derive wallets
	// 注册钱包事件处理程序以打开和自动派生钱包
	events := make(chan accounts.WalletEvent, 16)
	stack.AccountManager().Subscribe(events)

	// Create a client to interact with local geth node.
	// 创建一个与本地geth节点交互的客户端。
	rpcClient, err := stack.Attach()
	if err != nil {
		utils.Fatalf("Failed to attach to self: %v", err)
	}
	ethClient := ethclient.NewClient(rpcClient)

	// 监听钱包的进程
	go func() {
		// 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)

				var derivationPaths []accounts.DerivationPath
				if event.Wallet.URL().Scheme == "ledger" {
					derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)
				}
				derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)

				event.Wallet.SelfDerive(derivationPaths, ethClient)

			case accounts.WalletDropped:
				log.Info("Old wallet dropped", "url", event.Wallet.URL())
				event.Wallet.Close()
			}
		}
	}()

	// Spawn a standalone goroutine for status synchronization monitoring,
	// close the node when synchronization is complete if user required.
	// 生成一个独立的goroutine用于状态同步监视,如果用户需要,当同步完成时关闭节点。
	if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) {
		go func() {
			sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
			defer sub.Unsubscribe()
			for {
				event := <-sub.Chan()
				if event == nil {
					continue
				}
				done, ok := event.Data.(downloader.DoneEvent)
				if !ok {
					continue
				}
				if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
					log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
						"age", common.PrettyAge(timestamp))
					stack.Close()
				}
			}
		}()
	}

	// Start auxiliary services if enabled
	// 启动辅助服务
	if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
		// 只有当一个完整的以太坊节点正在运行时,挖矿才有意义
		if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
			utils.Fatalf("Light clients do not support mining")
		}
		ethBackend, ok := backend.(*eth.EthAPIBackend)
		if !ok {
			utils.Fatalf("Ethereum service not running")
		}
		// Set the gas price to the limits from the CLI and start mining
		gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
		ethBackend.TxPool().SetGasPrice(gasprice)
		// start mining
		threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
		if err := ethBackend.StartMining(threads); err != nil {
			utils.Fatalf("Failed to start mining: %v", err)
		}
	}
}

流程

总结

整个启动过程其实就是解析参数。然后创建和启动节点。 然后把服务注入到节点中。 所有跟以太坊相关的功能都是以服务的形式实现的,类似于微服务的形式。

目前所有的常驻的goroutine有下面一些。 主要是p2p相关的服务。 以及RPC相关的服务。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990528.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存