主页 > imtoken官网唯一地址 > 以太坊启动流程源码分析

以太坊启动流程源码分析

imtoken官网唯一地址 2023-06-17 06:18:27

文章资料(开源):github地址

启动参数

以太坊如何启动一个网络节点?

./geth --datadir "../data0" --nodekeyhex "27aa615f5fa5430845e4e97229def5f23e9525a20640cc49304f40f3b43824dc" --bootnodes $enodeid --mine --debug --metrics --syncmode="full" --gcmode=archive  --gasprice 0 --port 30303 --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpcapi "db,eth,net,web3,personal" --nat any --allow-insecure-unlock  2>>log 1>>log 0>>log >>log &

参数说明:

详细的以太坊启动参数可以参考我的以太坊理论系列,里面有参数的详细解释。

源码分析

geth位于cmd/geth/main.go文件中,入口如下:

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

sitejianshu.com 以太坊以太经典那个好_以太坊重启_sitebihu.com 以太以太坊价格

图片-20201012152238541

从这张图我们可以看出,main()并不是一个真正的入口。 常量和变量初始化后,会先调用模块的init()函数,再调用main()函数。 所以初始化工作是在init()函数中完成的。

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-2019 The go-ethereum Authors"
    app.Commands = []cli.Command{
    ....
    ....
    ...
  }

从这里我们找到了入口函数geth:

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

主要做了以下几件事:

准备运行内存缓存配额和设置指标系统负载配置和注册服务启动节点守护进程当前线程负载配置和注册服务

制作全节点

1.加载配置

制作配置节点

首先加载默认配置(作为主网节点启动):

cfg := gethConfig{
        Eth:  eth.DefaultConfig,
        Shh:  whisper.DefaultConfig,
        Node: defaultNodeConfig(),
    }

然后加载自定义配置(适用于私链):

if file := ctx.GlobalString(configFileFlag.Name); file != "" {
    if err := loadConfig(file, &cfg); err != nil {
        utils.Fatalf("%v", err)
    }
}

最后加载命令窗口参数(开发阶段):

utils.SetNodeConfig(ctx, &cfg.Node) // 本地节点配置
utils.SetEthConfig(ctx, stack, &cfg.Eth)// 以太坊配置
utils.SetShhConfig(ctx, stack, &cfg.Shh)// whisper配置

2.注册EthService

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)
    }
}

出现了两种新类型:ServiceContext 和 Service。

先看ServiceContext的定义:

type ServiceContext struct {
    config         *Config
    services       map[reflect.Type]Service // Index of the already constructed services
    EventMux       *event.TypeMux           // Event multiplexer used for decoupled notifications
    AccountManager *accounts.Manager        // Account manager created by the node.
}

ServiceContext主要存储一些从节点(或协议栈)继承而来的与具体Service无关的信息,如节点配置、账户管理器等。其中有一个services字段,保存了当前所有正在运行的Service。

接下来看Service的定义:

type Service interface {
    // Protocols retrieves the P2P protocols the service wishes to start.
    // 协议检索服务希望启动的P2P协议
    Protocols() []p2p.Protocol
    // APIs retrieves the list of RPC descriptors the service provides
    // API检索服务提供的RPC描述符列表
    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.
    //在所有服务都已构建完毕并且网络层也已初始化以生成服务所需的所有goroutine之后,将调用start。
    Start(server *p2p.Server) error
    // Stop terminates all goroutines belonging to the service, blocking until they
    // are all terminated.
    //Stop终止属于该服务的所有goroutine,直到它们全部终止为止一直阻塞。
    Stop() error
}

在服务注册过程中,主要注册了四个服务:EthService、DashboardService、ShhService、EthStatsService。 这四个服务类都是从Service接口扩展出来的。 其中,EthService根据不同的同步方式分为两种实现:

作为一个轻客户端,LightEthereum 与以太坊的不同之处在于它只需要更新区块头。 当需要查询区块体数据时,需要调用其他全节点的les服务进行查询; 另外以太坊重启,轻客户端本身是不能挖矿的。

回到RegisterEthService代码,分为两部分:

LightSync 同步:

err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
        return les.New(ctx, cfg)
    })

func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
  
  1.ctx.OpenDatabase // 创建leveldb数据库
  2.core.SetupGenesisBlockWithOverride// 根据创世配置初始化链数据目录
  3.实例化本地链id、共识引擎、注册peer节点、帐户管理器以及布隆过滤器的初始化
  4.light.NewLightChain// 使用数据库中可用的信息返回完全初始化的轻链。它初始化默认的以太坊头
  5.light.NewTxPool // 实例化交易池NewTxPool
  6.leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil} 
  
}

全同步/快速同步:

参数检查

if config.SyncMode == downloader.LightSync {
  ....
if !config.SyncMode.IsValid() {
  ....
if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 {
  ....
if config.NoPruning && config.TrieDirtyCache > 0 {  

打开数据库

ctx.OpenDatabaseWithFreezer

根据创世配置初始化链数据目录

core.SetupGenesisBlockWithOverride

实例化一个以太坊对象

创建一个 BlockChain 实例对象

core.NewBlockChain

实例化交易池

core.NewTxPool

实例化协议管理器

NewProtocolManager(...)

实例化外部 API 服务

&EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil}

3.注册ShhService

为 p2p 网络之间的加密通信注册 Whisper 服务。

whisper.New(cfg), nil

4.注册EthStatsService

注册状态推送服务,将当前以太坊网络状态推送到指定的URL地址。

ethstats.New(url, ethServ, lesServ)

起始节点

启动本地节点并启动所有已注册的服务。

1.启动节点

起始节点

1.1 堆栈。 开始()

实例化一个 p2p.Server 对象。

running := &p2p.Server{Config: n.serverConfig}

为注册服务创建上下文

for _, constructor := range n.serviceFuncs {
  ctx := &ServiceContext{
    ....
  }
}

收集协议并启动新组装的 p2p 服务器

for kind, service := range services {
  if err := service.Start(running); err != nil {
    ...
  }
}

最后启动配置好的RPC接口

n.startRPC(services)

2.解锁账号

解锁账户

datadir/keystore目录主要用于记录当前节点创建的账户keystore文件。 如果您的密钥库文件不在本地,则无法解锁。

//解锁datadir/keystore目录中帐户
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
    passwords := utils.MakePasswordList(ctx)
    for i, account := range unlocks {
        unlockAccount(ks, account, i, passwords)
    }

3.注册钱包事件

events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)

4.监控钱包事件

    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()
            }
        }
    }()

5.开始挖矿

ethereum.StartMining(threads)

启动守护线程

停止通道阻塞当前线程以太坊重启,直到节点停止。

node.Wait()

总结

以太坊启动主要做了三件事,加载配置注册服务,启动节点相关服务,启动守护线程。