P2P网络数据处理流程
监听(ListenLoop)+拨号(Dial) –> 建立连接(SetupConn) –> Enc 握手(doEncHandshake) –> 协议握手(doProtoHandshake) –> 添加Peer Addpeer –> Run Peer
1. Enc握手 doEncHandshake
监听时接收到Enc握手:
receiverEncHandshake
拨号时发起初始End握手:
initiatorEncHandshake
链接的发起者被称为initiator(主动拨号),链接的被动接受者被成为receiver(被动监听)。 这两种模式下处理的流程是不同的,完成握手后, 生成了一个sec可以理解为拿到了对称加密的密钥。 然后创建了一个newRLPXFrameRW帧读写器,完成加密信道的创建过程。
initiatorEncHandshake 和receiverEncHandshake有些像,但逻辑处理是相反的过程。
makeAuthMsg
makeAuthMsg这个方法创建了handshake message。 首先对端的公钥可以通过对端的ID来获取。对端的公钥对于发起者来说是知道的;对于接收者来说是不知道的。
根据对端的ID计算出对端公钥remotePub
生成一个随机的初始值initNonce
生成一个随机的私钥
使用自己的私钥和对方的公钥生成的一个共享秘密
用共享秘密来加密这个initNonce
这里把发起者的公钥告知对方
这一步,主要是构建authMsgV4结构体。
sealEIP8
sealEIP8对msg进行rlp的编码,填充一下数据,然后使用对方的公钥把数据进行加密。
readHandshakeMsg
readHandshakeMsg有两个地方调用: 一个是在initiatorEncHandshake,另外一个就是在receiverEncHandshake。 这个方法比较简单, 首先用一种格式尝试解码,如果不行就换另外一种。基本上就是使用自己的私钥进行解码然后调用rlp解码成结构体。 结构体的描述就是authRespV4,里面最重要的就是对端的随机公钥。 双方通过自己的私钥和对端的随机公钥可以得到一样的共享秘密。 而这个共享秘密是第三方拿不到的。
secrets
secrets函数是在handshake完成之后调用。它通过自己的随机私钥和对端的公钥来生成一个共享秘密,这个共享秘密是瞬时的(只在当前这个链接中存在)。
这个函数计算出IngressMAC和EgressMAC用于rlpxFrameRW中ReadMsg,WriteMsg数据的接收发送。
数据帧结构
normal = not chunked
chunked-0 = First frame of a multi-frame packet
chunked-n = Subsequent frames for multi-frame packet
|| is concatenate
^ is xor
Single-frame packet:
header || header-mac || frame || frame-mac
Multi-frame packet:
header || header-mac || frame-0 ||
[ header || header-mac || frame-n || … || ]
header || header-mac || frame-last || frame-mac
header: frame-size || header-data || padding
frame-size: 3-byte integer size of frame, big endian encoded (excludes padding)
header-data:
padding: zero-fill to 16-byte boundary
header-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digest
frame:
2. 协议握手doProtoHandshake
这个方法比较简单,加密信道已经创建完毕。 我们看到这里只是约定了是否使用Snappy加密然后就退出了。
在这个函数,发送给对方 handshakeMsg = 0x00,在readProtocolHandshake中读取接收对方发过来的handshakeMsg。
3. RLPX 数据分帧
在完成Encode握手之后,调用newRLPXFrameRW方法创建rlpxFrameRW对象,这的对象提供ReadMsg和WriteMsg方法
ReadMsg
1读取帧头header
2 验证帧头MAC
3 获取帧体Frame大小
4 读取帧体数据
5 验证帧体MAC信息
6 解密帧体内容(NewCTR à XORKeyStream)
7 解码帧体(RLP Decode)
8 解析帧体结构(msg.Size & msg.Payload)
9 snappy解码
WriteMsg
1 RLP编码msg.Code
2 如果snappy,就对读取payload并进行snappy编码
3 写帧头header (32字节)
4 写帧头MAC
5 写帧体信息(ptype+payload+padding)
6 写帧体MAC
4. runPeer
newPeerHook,建立peer的钩子函数
广播PeerEventTypeAdd事件
运行protocol
广播PeerEventTypeDrop事件
删除peer
run protocol
1 启动协程readLoop,读取消息并根据msg.Code处理消息:
pingMsg->pongMsg
discMsg->RLP解码msg.Payload返回reason
其他协议消息处理,根据msg.Code的取值范围,把msg分给注册的协议进行处理。
2 启动协程pingLoop
根据pingInterval(15秒)定时发送pingMsg消息
3 启动协议
startProtocols主要功能是启动协程运行注册协议的run函数proto.Run(p, rw),这个rw参数类型是protoRW,它实现的ReadMsg和WriteMsg增加msg.Code取值范围的处理。不同的protocol有不同的code取值范围,根据offset和Length确定。
感谢HPB团队整理。
本文首发于汪晓明博客
http://wangxiaoming.com/
汪晓明
HPB芯链创始人,巴比特专栏作家。十余年金融大数据、区块链技术开发经验,曾参与创建银联大数据。主创区块链教学视频节目《明说》30多期,编写了《以太坊官网文档中文版》,并作为主要作者编写了《区块链开发指南》,在中国区块链社区以ID“蓝莲花”知名。
领取专属 10元无门槛券
私享最新 技术干货