以太坊源码分析(五) - geth的启动过程分析

先看下启动的日志:

y@ubuntu:~/blockchain/Ethereum/go-ethereum-1.8.13$ build/bin/geth --identity "TestNode1" --datadir "data0" --rpc --rpcapi "db,eth,net,web3" --port "30303" --networkid "29382" --ws --wsorigins="*" --rpccorsdomain="*" console
INFO [11-06|23:35:10.501] Maximum peer count                       ETH=25 LES=0 total=25
INFO [11-06|23:35:10.502] Starting peer-to-peer node               instance=Geth/TestNode1/v1.8.13-stable/linux-amd64/go1.10.1
INFO [11-06|23:35:10.502] Allocated cache and file handles         database=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth/chaindata cache=768 handles=512
INFO [11-06|23:35:10.522] Writing default main-net genesis block 
INFO [11-06|23:35:10.919] Persisted trie from memory database      nodes=12356 size=1.88mB time=80.670674ms gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [11-06|23:35:10.920] Initialised chain configuration          config="{ChainID: 1 Homestead: 1150000 DAO: 1920000 DAOSupport: true EIP150: 2463000 EIP155: 2675000 EIP158: 2675000 Byzantium: 4370000 Constantinople:  Engine: ethash}"
INFO [11-06|23:35:10.920] Disk storage enabled for ethash caches   dir=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth/ethash count=3
INFO [11-06|23:35:10.920] Disk storage enabled for ethash DAGs     dir=/home/y/.ethash                                                  count=2
INFO [11-06|23:35:10.920] Initialising Ethereum protocol           versions="[63 62]" network=29382
INFO [11-06|23:35:10.920] Loaded most recent local header          number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Loaded most recent local full block      number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Loaded most recent local fast block      number=0 hash=d4e567…cb8fa3 td=17179869184
INFO [11-06|23:35:10.920] Regenerated local transaction journal    transactions=0 accounts=0
INFO [11-06|23:35:10.920] Starting P2P networking 
INFO [11-06|23:35:13.046] UDP listener up                          self=enode://93348ffc72a60b28baabf268158749e187e393b5986a3fed9c1f2d6dfe76d3d232a984e74cf7034e6ea9a7607550bf58056c59a1fd812d4caa8ca7989f0ce0f0@[::]:30303
INFO [11-06|23:35:13.047] RLPx listener up                         self=enode://93348ffc72a60b28baabf268158749e187e393b5986a3fed9c1f2d6dfe76d3d232a984e74cf7034e6ea9a7607550bf58056c59a1fd812d4caa8ca7989f0ce0f0@[::]:30303
INFO [11-06|23:35:13.048] IPC endpoint opened                      url=/home/y/blockchain/Ethereum/go-ethereum-1.8.13/data0/geth.ipc
INFO [11-06|23:35:13.049] HTTP endpoint opened                     url=http://127.0.0.1:8545                                         cors=* vhosts=localhost
INFO [11-06|23:35:13.052] WebSocket endpoint opened                url=ws://127.0.0.1:8546
Welcome to the Geth JavaScript console!

instance: Geth/TestNode1/v1.8.13-stable/linux-amd64/go1.10.1
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> 

分析下源码

一、启动的main函数 cmd/geth/main.go

Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。

在cmd/geth/main.go中,首先定义app:

var (
	// Git SHA1 commit hash of the release (set via linker flags)
	gitCommit = ""
	// The app that holds all commands and flags.
	app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
	// flags that configure the node
	nodeFlags = []cli.Flag{
		utils.IdentityFlag,
		utils.UnlockedAccountFlag,
		utils.PasswordFileFlag,
		.....

然后通过init()函数来初始化app:

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.Action表示如果用户没有输入其他的子命令的情况下,会调用这个字段指向的函数,即geth()。

geth的用法是

  geth [options] command [command options] [arguments...]

app.Flags和app.Commands分别设置了对应的的[options]和command

通过上面的init()方法就把用户输入的命令解析完成了,保存在对象app中,下一步执行main()方法:

func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

主要就是调用app.Run()方法:

func (a *App) Run(arguments []string) (err error) {
	a.Setup()		//初始化数据结构

	// handle the completion flag separately from the flagset since
	// completion could be attempted after a flag, but before its value was put
	// on the command line. this causes the flagset to interpret the completion
	// flag name as the value of the flag before it which is undesirable
	// note that we can only do this because the shell autocomplete function
	// always appends the completion flag at the end of the command
	shellComplete, arguments := checkShellCompleteFlag(a, arguments)

	// parse flags
	set, err := flagSet(a.Name, a.Flags)
	if err != nil {
		return err
	}

	set.SetOutput(ioutil.Discard)
	err = set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set)
	context := NewContext(a, set, nil)
	if nerr != nil {
		fmt.Fprintln(a.Writer, nerr)
		ShowAppHelp(context)
		return nerr
	}
	context.shellComplete = shellComplete

	if checkCompletions(context) {
		return nil
	}

	if err != nil {
		if a.OnUsageError != nil {
			err := a.OnUsageError(context, err, false)
			HandleExitCoder(err)
			return err
		}
		fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
		ShowAppHelp(context)
		return err
	}

	//显示帮助信息
	if !a.HideHelp && checkHelp(context) {
		ShowAppHelp(context)
		return nil
	}

	if !a.HideVersion && checkVersion(context) {
		ShowVersion(context)
		return nil
	}

	if a.After != nil {
		defer func() {
			if afterErr := a.After(context); afterErr != nil {
				if err != nil {
					err = NewMultiError(err, afterErr)
				} else {
					err = afterErr
				}
			}
		}()
	}

	if a.Before != nil {
		beforeErr := a.Before(context)
		if beforeErr != nil {
			ShowAppHelp(context)
			HandleExitCoder(beforeErr)
			err = beforeErr
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	if a.Action == nil {
		a.Action = helpCommand.Action
	}

	// Run default Action
	err = HandleAction(a.Action, context)

	HandleExitCoder(err)
	return err
}

最终会调用HandleAction()方法执行默认的action:

// HandleAction attempts to figure out which Action signature was used.  If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
	if a, ok := action.(ActionFunc); ok {
		return a(context)
	} else if a, ok := action.(func(*Context) error); ok {
		return a(context)
	} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
		a(context)
		return nil
	} else {
		return errInvalidActionType
	}
}

而默认的action就是之前分析的init()方法中通过

app.Action = geth

设置的main.go中的geth()方法:

// geth is the main entry point into the system if no special subcommand is ran.
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
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()方法创建一个全节点对象,然后通过startNode()方法来启动该节点,下面详细分析下这两个方法。

makeFullNode()方法

func makeFullNode(ctx *cli.Context) *node.Node {
	stack, cfg := makeConfigNode(ctx) //创建节点和配置信息,配置包括Eth,Shh,Node,Dashboard

	utils.RegisterEthService(stack, &cfg.Eth)  // 注册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)
		}
		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(),加载配置并创建节点
先调用 makeConfigNode() 方法来加载配置并创建节点,返回节点对象stack和配置对象cfg,程序里把节点叫做协议栈(protocol stack),所以可以看到,节点对象被命名为stack。

func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
	// Load defaults.
	cfg := gethConfig{
		Eth:       eth.DefaultConfig,    //配置eth的一些基本信息,里面设置了NetWorkId(默认值为1,这也是eth的主网)的信息
		Shh:       whisper.DefaultConfig,    //里面配置了两个参数:1.MaxMessageSize 2.MinimumAcceptedPOW(POW:Proof of Work,工作量证明)
		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)   //这里设置了P2P,IPC,HTTP,WS,DataDir,KeyStoreDir的值
	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
}

RegisterEthService(),注册eth服务
上面创建好节点和配置信息后,再调用cmd/utils/flags.go里的RegisterEthService()方法注册eth服务,

// RegisterEthService adds an Ethereum client to the stack.
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) {
			fullNode, err := eth.New(ctx, cfg)
			if fullNode != nil && cfg.LightServ > 0 {
				ls, _ := les.NewLesServer(fullNode, cfg)
				fullNode.AddLesServer(ls)
			}
			return fullNode, err
		})
	}
	if err != nil {
		Fatalf("Failed to register the Ethereum service: %v", err)
	}
}

这个函数里会判断同步的模式 ,如果是LightSync模式(该模式下只会下载区块头)则会使用les.New()创建轻节点,否则就使用eth.New()创建全节点,这里是建立全节点,即调用eth.New()方法:

// New creates a new Ethereum object (including the
// initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
	if config.SyncMode == downloader.LightSync {
		return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
	}
	if !config.SyncMode.IsValid() {
		return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
	}
	//创建leveldb,leveldb是以太坊区块数据的存储数据库
	chainDb, err := CreateDB(ctx, config, "chaindata")
	if err != nil {
		return nil, err
	}
	
	//装载创世区块。 根据节点条件判断是从数据库里面读取,还是从默认配置文件读取,还是从自定义配置文件读取,
	//或者是从代码里面获取默认值。并返回区块链的config和创世区块的hash。	
	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis)
	if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
		return nil, genesisErr
	}
	log.Info("Initialised chain configuration", "config", chainConfig)
	
	//初始化以太坊对象eth,装载Etherum struct的各个成员。eventMux和accountManager是Node启动eth service的时候传入的。
	//eventMux可以认为是一个全局的事件多路复用器,accountManager认为是一个全局的账户管理器。engine创建共识引擎。
	//etherbase配置此Etherum的主账号地址。初始化bloomRequests通道和bloom过滤器。
	eth := &Ethereum{
		config:         config,
		chainDb:        chainDb,
		chainConfig:    chainConfig,
		eventMux:       ctx.EventMux,
		accountManager: ctx.AccountManager,
		engine:         CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb),
		shutdownChan:   make(chan bool),
		networkID:      config.NetworkId,
		gasPrice:       config.GasPrice,
		etherbase:      config.Etherbase,
		bloomRequests:  make(chan chan *bloombits.Retrieval),
		bloomIndexer:   NewBloomIndexer(chainDb, params.BloomBitsBlocks),
	}

	log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId)

	//判断数据库版本号和客户端版本号是否一致,如果不一致,提示更新客户端
	if !config.SkipBcVersionCheck {
		bcVersion := rawdb.ReadDatabaseVersion(chainDb)
		if bcVersion != core.BlockChainVersion && bcVersion != 0 {
			return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, core.BlockChainVersion)
		}
		rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
	}
	var (
		vmConfig    = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording}
		cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}
	)
	
	//使用数据库里面的可用信息构造了一个初始化好的区块链,同时初始化了以太坊默认的验证器和处理器 (Validator and Processor)
	eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig)
	if err != nil {
		return nil, err
	}
	// Rewind the chain in case of an incompatible config upgrade.
	if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
		log.Warn("Rewinding chain to upgrade configuration", "err", compat)
		eth.blockchain.SetHead(compat.RewindTo)
		rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
	}
	//启动bloomIndexer.
	eth.bloomIndexer.Start(eth.blockchain)

	if config.TxPool.Journal != "" {
		config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
	}
	//创建交易池,收集、排序以及过滤节点发送来的交易
	eth.txPool = core.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
	//创建以太坊协议管理器,管理对等节点的P2P网络通信功能
	if eth.protocolManager, err = NewProtocolManager(eth.chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb); err != nil {
		return nil, err
	}
	//创建矿工,矿工用于创建区块和寻找工作量证明
	eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
	eth.miner.SetExtra(makeExtraData(config.ExtraData))

	eth.APIBackend = &EthAPIBackend{eth, nil}
	gpoParams := config.GPO
	if gpoParams.Default == nil {
		gpoParams.Default = config.GasPrice
	}
	// 创建预言最新gasprice的预言机
	eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)

	return eth, nil
}

Ethereum作为一个service,注入进Node中,上面的方法实现的就是Ethereum service的初始化和启动过程,该过程配置了很多东西,基本上涉及到了以太坊区块链系统的所有内容。
总结下,这个函数主要做了以下工作:
1、创建leveldb数据库
2、加载创始区块
3、初始化以太坊对象eth
4、初始化区块链BlookChain
5、初始化交易池
6、初始化以太坊协议管理器
7、初始化矿工
8、初始化预言gasprice的预言机

startNode()方法

回到cmd/geth/main.go中,在节点配置完成后执行startNode()方法来启动节点,

// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
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.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(ðereum); 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)
		}
	}
}

根据源码可以看到,startNode()的主要功能有:
1、启动node;
2、解锁账户;
3、开启钱包事件监听;

其中utils.StartNode(stack)方法会调用node/node.go中的Start()方法来创建一个 P2P 节点并启动它:

// Start create a live P2P node and starts running it.
func (n *Node) Start() error {
	n.lock.Lock()
	defer n.lock.Unlock()

	//首先判断节点是否已经在运行了
	// Short circuit if the node's already running
	if n.server != nil {
		return ErrNodeRunning
	}
	if err := n.openDataDir(); err != nil {
		return err
	}

	// 初始化 p2p server
	// Initialize the p2p server. This creates the node key and
	// discovery databases.
	n.serverConfig = n.config.P2P
	n.serverConfig.PrivateKey = n.config.NodeKey()
	n.serverConfig.Name = n.config.NodeName()
	n.serverConfig.Logger = n.log
	if n.serverConfig.StaticNodes == nil {
		n.serverConfig.StaticNodes = n.config.StaticNodes()
	}
	if n.serverConfig.TrustedNodes == nil {
		n.serverConfig.TrustedNodes = n.config.TrustedNodes()
	}
	if n.serverConfig.NodeDatabase == "" {
		n.serverConfig.NodeDatabase = n.config.NodeDB()
	}
	//构建p2p.Server对象
	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
		}
		// 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
	}
	// Gather the protocols and start the freshly assembled P2P server
	for _, service := range services {
		running.Protocols = append(running.Protocols, service.Protocols()...)
	}
	//调用p2p.Server对象的Start()方法,启动p2p Server
	if err := running.Start(); err != nil {
		return convertFileLockError(err)
	}

	// 启动各个服务
	// Start each of the services
	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
	// 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
}

在这个方法中,首先判断节点是否已经在运行,然后对p2p Server进行初始化,再构建p2p.Server对象running,然后执行该对象的Start()方法来启动p2p Server,最后启动各个services服务,启动RPC。

至此,以太坊节点的启动过程就完成了。

参考:
以太坊源码解读(3)以太坊启动流程简析
以太坊源码深入分析(5)-- Ethereum服务和以太坊P2P协议发送广播源码分析

以太坊源码深入分析(2)-- go-ethereum 客户端入口代码和Node分析

以太坊网络架构解析

你可能感兴趣的:(区块链,以太坊源码分析)