以太坊启动流程分析
pyethapp启动过程
pyethapp的启动实际上就是启动各个服务的过程。pyethapp提供的服务包括:
# 注意: 顺序代表服务启动的顺序, 后面的服务依赖于前面服务的启动
services = [
DBService,
AccountsService,
NodeDiscovery,
PeerManager,
ChainService,
PoWService,
JSONRPCServer,
IPCRPCServer,
Console]
所有服务都注册在ethApp的实例上:
主流程启动
- main入口在app.py, 只有一个函数调用: app(), 该函数主要功能是根据输入参数以及默认配置生成一个config词典,便于后面各个服务获取相关的配置.
- 执行子命令run(...):
- 配置p2p的默认监听端口=29873
- 创建一个app实例:
app=EthApp(BaseApp)
, 其中BaseApp是pydevp2p组件提供的, 主要用于服务的注册(register_service
) , 并负责启动服务(service.start()
) . - 实例化一个AccountsService服务, 并注册到EthApp
- 注册AccountsService服务后,就可以处理账户相关的操作了。
- 启动参数中指定unlock的账户, 对该账户进行解锁,;
- 检查coinbase是否已经配置,没有配置的话设置为默认的accounts[0];
- 如果配置需要启动console, 记录启动标识符:
app.start_console = console
- 检查是否有用户自定义的服务。
用户可以自定义服务, 将py文件放在pyethapp/contrib下, module的名字就是文件名
1\. 获取该module下继承BaseService的类;
1\. 获取on_block和on_start函数,生产成一个 `_OnBlockCallbackService` 服务,该服务下start函数和cb函数对应为on_block和on_start函数, 该服务作为用户自定义服务;
- 实例化并注册上面services 中的所有服务;
- 启动app:
app.start()
, 实际上就是启动所有服务:service.start()
;
各个服务的主要初始化和启动
在上面注册和实例化服务过程中,实际上是初始化和启动服务的过程。下面分析下各个服务的初始化流程。
DBService(levelDB/LmDB/CodernityDB/EphemDB Service)服务
顾名思义该服务和数据库相关,主要动作是根据配置实例化一个数据库, 默认 db_service = levelDB
; 该数据库用来持久化所有区块链相关的数据
AccountsService服务
该服务除了处理unlock参数之外,还要:
- 解析keystore文件夹中的账户文件, 并保存公钥私钥到accounts数组中;
- 配置coinbase地址:默认是accounts[0],如果配置了pow.coinbase_hex, coinbase就是pow.coinbase_hex;
NodeDiscovery服务
该服务提供节点发现功能,是p2p的底层协议。p2p节点发现的默认监听地址是: 0.0.0.0:30303
-
首先实例化一个发现协议:
DiscoveryProtocol(app=self.app, transport=self)
-
然后start(): 启动过一个udp服务,监听报文, 报文处理函数是
_handle_packet
:self.server = DatagramServer((ip, port), handle=self._handle_packet)
-
配置bootstrap...
WiredService服务: p2p网络的线上服务. 提供2个接口。
on_wire_protocol_start(proto)
on_wire_protocol_stop(proto)
继承该类的服务可以自定义接口的实现, 系统中有2个服务基层了该服务:
PeerManager继承自该服务. 初始化如下:
-
每个node都有一个私钥,根据私钥配置nodeid, 配置监听的
host : port
-
在p2p的监听地址上创建一个TCP服务端, 接受新连接并为每个连接创建一个协程,协程函数是用户提供的:
_on_new_connection
self.server = StreamServer(self.listen_addr,
handle=self._on_new_connection)
-
配置线上协议
wire_protocol = P2PProtocol
。这个线上协议实现的就是协议层的功能。 -
start(): 启动上面创建的tcp服务端, 延时0.001秒后启动一个线程连接bootstrap_nodes:
_bootstrap(bootstrap_nodes)
5.1 从bootstrap_nodes的uri中获取对端的ip, port, pubkey
5.2 建立socket连接, 连接成功, 创建一个Peer实例,并维护长连接_start_peer()
,详见p2p服务章节. -
start(): 延时1s,启动任务
_discovery_loop
每隔0.5秒就检查节点, 从节点发现协议kademlia中获取节点信息,并连接。
ChainService继承自该服务。初始化如下:
-
配置线上协议
wire_protocol = ETHProtocol
。这个线上协议实现的就是协议层的功能; -
实例化一个Chain数据结构, 配置包括db,创世块数据,矿工地址,及new_head_cb的处理函数;
-
新建一个block同步器Synchronizer, 新建一个block队列block_queue, 新建一个交易池TransactionQueue和广播过滤器DuplicatesFilter;
-
启动一个处理time_queue的定时任务, 周期5s, time_queue用来保存时间戳混乱的block;
PoWService
- 启动一个进程 powworker_process
- 注册chain的一个回调处理函数:on_new_head_cbs=mine_head_candidate
console服务
提供控制台操作入口
RPCServer
rpc 命令包括 Web3, Personal, Net, Compilers, DB, Chain, Miner, FilterManager
rpc相关的详细描述见: https://www.notion.so/RPC-9eda75aaa51a4277a2aeb4cd9ae3693a
初始化阶段启用下面2个服务:
-
JSONRPCServer 继承自该服务. 初始化如下:
1.1. 注册上面的rpc子命令到dispatcher
1.2. 启动wsgi服务和jsonrpc服务
transport =WsgiServerTransport( queue_class=gevent.queue.Queue, allow_origin=self.config['jsonrpc']['corsdomain'])
self.wsgi_server = gevent.wsgi.WSGIServer(listener, transport.handle, log=WSGIServerLogger) self.rpc_server = RPCServerGreenlets( transport, JSONRPCProtocol(), self.dispatcher )
1.3. 启动wsgi服务和jsonrpc服务
self.wsgi_thread = gevent.spawn(self.wsgi_server.serve_forever) self.rpc_server.serve_forever()
-
IPCRPCServer继承自该服务. 初始化如下:
2.1 默认ipcpath='/tmp/pyethapp.ipc'
2.2 实例化一个分发器: LoggingDispatcher(RPCDispatcher)
2.3 启动1个rpc-server
self.transport = IPCDomainSocketTransport( queue_class=gevent.queue.Queue, sockpath=self.ipcpath, )
self.rpc_server = RPCServerGreenlets( self.transport, JSONRPCProtocol(), self.dispatcher )
2.4. 启动socket服务和rpc服务self.socket_server = gevent.spawn(serve, self.transport.socket, handler=self.transport.handle) self.rpc_server.serve_forever()
validator Service
和casper混合挖矿有关, 目前版本还在开发中。