死磕以太坊源码|以太坊启动流程
文章以及资料(开源):github地址
启动参数
以太坊是如何启动一个网络节点的呢?
1 | ./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 : 编译好的geth程序,可以起别名
- datadir:数据库和keystore密钥的数据目录
- nodekeyhex: 十六进制的P2P节点密钥
- bootnodes:用于P2P发现引导的enode urls
- mine:打开挖矿
- debug:突出显示调用位置日志(文件名及行号)
- metrics: 启用metrics收集和报告
- syncmode:同步模式 (“fast”, “full”, or “light”)
- gcmode:表示即时将内存中的数据写入到文件中,否则重启节点可能会导致区块高度归零而丢失数据
- gasprice:挖矿接受交易的最低gas价格
- port:网卡监听端口(默认值:30303)
- rpc:启用HTTP-RPC服务器
- rpcaddr:HTTP-RPC服务器接口地址(默认值:“localhost”)
- rpcport:HTTP-RPC服务器监听端口(默认值:8545)
- rpcapi:基于HTTP-RPC接口提供的API
- nat: NAT端口映射机制 (any|none|upnp|pmp|extip:
) (默认: “any”) - allow-insecure-unlock:用于解锁账户
详细的以太坊启动参数可以参考我的以太坊理论系列,里面有对参数的详细解释。
源码分析
geth
位于cmd/geth/main.go
文件中,入口如下:
1 | func main() { |
我们通过这张图可以看出来:main()并不是真正意义上的入口,在初始化完常量和变量以后,会先调用模块的init()函数,然后才是main()函数。所以初始化的工作是在init()函数里完成的。
1 | func init() { |
从这我们找到了入口函数geth:
1 | func geth(ctx *cli.Context) error { |
主要做了以下几件事:
- 准备操作内存缓存配额并设置度量系统
- 加载配置和注册服务
- 启动节点
- 守护当前线程
加载配置和注册服务
makeFullNode
1.加载配置
makeConfigNode
首先加载默认配置(作为主网节点启动):
1 | cfg := gethConfig{ |
- eth.DefaultConfig : 以太坊节点的主要参数配置。主要包括: 同步模式(fast)、chainid、交易池配置、gasprice、挖矿配置等;
- whisper.DefaultConfig : 主要用于配置网络间通讯;
- defaultNodeConfig() : 主要用于配置对外提供的RPC节点服务;
- dashboard.DefaultConfig : 主要用于对外提供看板数据访问服务。
接着加载自定义配置(适用私有链):
1 | if file := ctx.GlobalString(configFileFlag.Name); file != "" { |
最后加载命令窗口参数(开发阶段):
1 | utils.SetNodeConfig(ctx, &cfg.Node) // 本地节点配置 |
2.RegisterEthService
1 | func RegisterEthService(stack *node.Node, cfg *eth.Config) { |
出现了两个新类型:ServiceContext和Service。
先看一下ServiceContext的定义:
1 | type ServiceContext struct { |
ServiceContext主要是存储了一些从结点(或者叫协议栈)那里继承过来的、和具体Service无关的一些信息,比如结点config、account manager等。其中有一个services字段保存了当前正在运行的所有Service.
接下来看一下Service的定义:
1 | type Service interface { |
在服务注册过程中,主要注册四个服务:EthService、DashboardService、ShhService、EthStatsService,这四种服务类均扩展自Service接口。其中,EthService根据同步模式的不同,分为两种实现:
- LightEthereum,支持LightSync模式
- Ethereum,支持FullSync、FastSync模式
LightEthereum作为轻客户端,与Ethereum区别在于,它只需要更新区块头。当需要查询区块体数据时,需要通过调用其他全节点的les服务进行查询;另外,轻客户端本身是不能进行挖矿的。
回到RegisterEthService代码,分两个来讲:
LightSync同步:
1 | err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { |
1 | func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { |
FullSync/Fast同步:
参数校验
1
2
3
4
5
6
7if 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 {打开数据库
1
ctx.OpenDatabaseWithFreezer
根据创世配置初始化链数据目录
1
core.SetupGenesisBlockWithOverride
实例化Ethereum对象
创建BlockChain实例对象
1
core.NewBlockChain
实例化交易池
1
core.NewTxPool
实例化协议管理器
1
NewProtocolManager(...)
实例化对外API服务
1
&EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil}
3.RegisterShhService
注册Whisper服务,用于p2p网络间加密通信。
1 | whisper.New(cfg), nil |
4.RegisterEthStatsService
注册状态推送服务,将当前以太坊网络状态推送至指定URL地址.
1 | ethstats.New(url, ethServ, lesServ) |
启动节点
启动本地节点以及启动所有注册的服务。
1.启动节点
startNode
1.1 stack.Start()
实例化p2p.Server对象。
1
running := &p2p.Server{Config: n.serverConfig}
为注册的服务创建上下文
1
2
3
4
5for _, constructor := range n.serviceFuncs {
ctx := &ServiceContext{
....
}
}收集协议并启动新组装的p2p server
1
2
3
4
5for kind, service := range services {
if err := service.Start(running); err != nil {
...
}
}最后启动配置的RPC接口
1
n.startRPC(services)
- startInProc (启动进程内通讯服务)
- startIPC (启动IPC RPC端点)
- startHTTP(启动HTTP RPC端点)
- startWS (启动websocket RPC端点)
2.解锁账户
unlockAccounts
在datadir/keystore目录主要用于记录在当前节点创建的帐户keystore文件。如果你的keystore文件不在本地是无法进行解锁的。
1 | //解锁datadir/keystore目录中帐户 |
3.注册钱包事件
1 | events := make(chan accounts.WalletEvent, 16) |
4.监听钱包事件
1 | for event := range events { |
5.启动挖矿
1 | ethereum.StartMining(threads) |
启动守护线程
stop通道阻塞当前线程,直到节点被停止。
1 | node.Wait() |
总结
以太坊启动主要就做了3件事,包括加载配置注册服务、启动节点相关服务以及启动守护线程。