以太坊源码分析(二):以太坊启动流程分析

栏目: 编程工具 · 发布时间: 6年前

内容简介:本章节主要通过分析源代码来了解以太坊的启动流程,本文基于以太坊的源码版本是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——

关注“布洛克科技”

以太坊源码分析(二):以太坊启动流程分析


以上所述就是小编给大家介绍的《以太坊源码分析(二):以太坊启动流程分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构 Python语言描述

数据结构 Python语言描述

[美] Kenneth A. Lambert 兰伯特 / 李军 / 人民邮电出版社 / 2017-12-1 / CNY 69.00

在计算机科学中,数据结构是一门进阶性课程,概念抽象,难度较大。Python语言的语法简单,交互性强。用Python来讲解数据结构等主题,比C语言等实现起来更为容易,更为清晰。 《数据结构 Python语言描述》第1章简单介绍了Python语言的基础知识和特性。第2章到第4章对抽象数据类型、数据结构、复杂度分析、数组和线性链表结构进行了详细介绍,第5章和第6章重点介绍了面向对象设计的相关知识、......一起来看看 《数据结构 Python语言描述》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具