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

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

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

关注“布洛克科技”

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


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

查看所有标签

猜你喜欢:

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

精通正则表达式

精通正则表达式

[美] Jeffrey E.F.Friedl / 余晟 / 电子工业出版社 / 2012-7 / 89.00元

《精通正则表达式(第3版)》内容简介:随着互联网的迅速发展,几乎所有工具软件和程序语言都支持的正则表达式也变得越来越强大和易于使用。《精通正则表达式(第3版)》是讲解正则表达式的经典之作。《精通正则表达式(第3版)》主要讲解了正则表达式的特性和流派、匹配原理、优化原则、实用诀窍以及调校措施,并详细介绍了正则表达式在Perl、Java、.NET、PHP中的用法。《精通正则表达式(第3版)》自第1版开......一起来看看 《精通正则表达式》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具