Skip to content

以太坊P2P

节点拿到NodeID:enode://af18b22fcb7de98ab4ef5e0d273d516e7d31d7ebca2362a2fcf9eff821da0b6a98bea6c091743f339a8010812d94e685dc370a97a57e5134dbefd753ccf8c5d8@127.0.0.1:30303

生成task := newDialTask(node, staticDialedConn),然后把这个task存在map中,key为id

task有一个成员函数dial,这个dial函数对外部节点发起了tcp拨号请求(使用的是golang标准库的net/dial.go),如果请求成功,就会返回一个conn(其实就是fd,文件描述符),然后就会把这个fd,对方的ip和port存起来。然后d.setupFunc(mfd, t.flags, dest)会执行RLPx handshake,包括EncHandshake和ProtoHandshake。case c := <-srv.checkpointAddPeer:被用来添加已经握手成功的节点。

case c := <-srv.checkpointAddPeer:中,会最后检查远程节点是否应该被连接。然后就会启动这个节点p := srv.launchPeer(c),其中会启动一个协程go srv.runPeer(p)

srv.runPeer(p)中,会启动这个节点的服务的主循环remoteRequested, err := p.run(),这个run函数会一直阻塞,直到节点断开才会返回remoteRequested, err,所以具体阅读srv.runPeer(p)这个函数就会发现,其先告知系统节点加入,然后p.run()启动了主循环,然后节点断开后向下执行,告知系统节点离开,需要删除并丢弃节点。主要看下p.run()干啥了:

p.run()首先启动了两个协程,一个读循环和一个ping循环。读循环会一直阻塞读,读到数据后就交给p.handle(msg)去处理读到的数据。ping循环会每15秒向远程节点发送一个ping。主要看下p.handle(msg)干啥了:

如果msg.Code是ping,那么就给对方发个pong。如果是discMsg,那么就断开连接,函数返回。如果是其他类型的msg,那么说明是子协议。这里子协议是指,比P2P协议更上层的协议,比如区块/交易同步等等。经调试,在这次调试中,我方节点启动了两个子协议eth和snap,那么msg会被自动发给eth或snap的消息通道中,交给各自的处理方法去处理。

本地交易池中有新交易或者挖到新区块时,节点会对外广播。具体代码全局搜索func (h *handler) Start(maxPeers int) {,这个函数也开启了同步协程,启动了block下载器和tx下载器。下载器代码有400行,先不看了。