55

P2P网络数据处理流程及数据交互

 5 years ago
source link: http://v1.8btc.com/p2p-shuju-jiaohu?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、P2P网络数据处理流程

监听(ListenLoop)+拨号(Dial) –> 建立连接(SetupConn) –> Enc 握手(doEncHandshake) –> 协议握手(doProtoHandshake) –> 添加Peer Addpeer –> Run Peer

1. Enc握手 doEncHandshake

BZJrMza.png!web

监听时接收到Enc握手:receiverEncHandshake

拨号时发起初始End握手:initiatorEncHandshake

链接的发起者被称为initiator(主动拨号),链接的被动接受者被成为receiver(被动监听)。 这两种模式下处理的流程是不同的,完成握手后, 生成了一个sec可以理解为拿到了对称加密的密钥。 然后创建了一个newRLPXFrameRW帧读写器,完成加密信道的创建过程。

initiatorEncHandshake 和receiverEncHandshake有些像,但逻辑处理是相反的过程。

FfaMfy3.png!web

FfaMfy3.png!web

makeAuthMsg

makeAuthMsg这个方法创建了handshake message。 首先对端的公钥可以通过对端的ID来获取。对端的公钥对于发起者来说是知道的;对于接收者来说是不知道的。

2uQ7Zzv.png!web

  • 根据对端的ID计算出对端公钥remotePub
  • 生成一个随机的初始值initNonce
  • 生成一个随机的私钥
  • 使用自己的私钥和对方的公钥生成的一个共享秘密
  • 用共享秘密来加密这个initNonce
  • 这里把发起者的公钥告知对方

这一步,主要是构建authMsgV4结构体。

j6VreeA.png!web

sealEIP8

sealEIP8对msg进行rlp的编码,填充一下数据,然后使用对方的公钥把数据进行加密。

bU32Y33.png!web

readHandshakeMsg

readHandshakeMsg有两个地方调用: 一个是在initiatorEncHandshake,另外一个就是在receiverEncHandshake。 这个方法比较简单, 首先用一种格式尝试解码,如果不行就换另外一种。基本上就是使用自己的私钥进行解码然后调用rlp解码成结构体。 结构体的描述就是authRespV4,里面最重要的就是对端的随机公钥。 双方通过自己的私钥和对端的随机公钥可以得到一样的共享秘密。 而这个共享秘密是第三方拿不到的。

eeaIzyZ.png!web

secrets

secrets函数是在handshake完成之后调用。它通过自己的随机私钥和对端的公钥来生成一个共享秘密,这个共享秘密是瞬时的(只在当前这个链接中存在)。

RZn2ymf.png!web

这个函数计算出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:

    normal: rlp.list(protocol-type[, context-id])

    chunked-0: rlp.list(protocol-type, context-id, total-packet-size)

    chunked-n: rlp.list(protocol-type, context-id)

    values:

        protocol-type: < 2**16

        context-id: < 2**16 (optional for normal frames)

        total-packet-size: < 2**32

padding: zero-fill to 16-byte boundary

header-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digest

frame:

    normal: rlp(packet-type) [|| rlp(packet-data)] || padding

    chunked-0: rlp(packet-type) || rlp(packet-data...)

    chunked-n: rlp(...packet-data) || padding

padding: zero-fill to 16-byte boundary (only necessary for last frame)

frame-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac) ^ right128(egress-mac.update(frame-ciphertext).digest))

egress-mac: h256, continuously updated with egress-bytes*

ingress-mac: h256, continuously updated with ingress-bytes*

2. 协议握手doProtoHandshake

这个方法比较简单,加密信道已经创建完毕。 我们看到这里只是约定了是否使用Snappy加密然后就退出了。

M3qiiez.png!web

在这个函数,发送给对方 handshakeMsg = 0×00,在readProtocolHandshake中读取接收对方发过来的handshakeMsg。

3. RLPX 数据分帧

iEnmueI.png!web

在完成Encode握手之后,调用newRLPXFrameRW方法创建rlpxFrameRW对象,这的对象提供ReadMsg和WriteMsg方法

ReadMsg

jYZbe2a.png!web )

1读取帧头header

2 验证帧头MAC

3 获取帧体Frame大小

zERVjeQ.png!web

4 读取帧体数据

5 验证帧体MAC信息

qaANRnY.png!web

qMrUJvY.png!web

6 解密帧体内容(NewCTR à XORKeyStream)

7 解码帧体(RLP Decode)

8 解析帧体结构(msg.Size & msg.Payload)

nQbUVrZ.png!web

9 snappy解码

WriteMsg

INJRr2E.png!web

1 RLP编码msg.Code

2 如果snappy,就对读取payload并进行snappy编码

auyEbib.png!web

3 写帧头header (32字节)

4 写帧头MAC

3YfQF3Y.png!web

5 写帧体信息(ptype+payload+padding)

6 写帧体MAC

4. runPeer

BN3qMbI.png!web

newPeerHook,建立peer的钩子函数

广播PeerEventTypeAdd事件

运行protocol

广播PeerEventTypeDrop事件

删除peer

run protocol

jYJRvan.png!web

1 启动协程readLoop,读取消息并根据msg.Code处理消息:

pingMsg->pongMsg

discMsg->RLP解码msg.Payload返回reason

其他协议消息处理,根据msg.Code的取值范围,把msg分给注册的协议进行处理。

jUbaaiy.png!web

2 启动协程pingLoop

根据pingInterval(15秒)定时发送pingMsg消息

mIrARrY.png!web

3 启动协议

startProtocols主要功能是启动协程运行注册协议的run函数proto.Run(p, rw),这个rw参数类型是protoRW,它实现的ReadMsg和WriteMsg增加msg.Code取值范围的处理。不同的protocol有不同的code取值范围,根据offset和Length确定。

ieaAzya.png!web

二、P2P网络数据交互

1. 发送交易数据SendTransactions

事件触发交易广播txBroadcastLoop

本地发送了一个交易,或者是接收到别人发来的交易信息。 txpool会产生一条消息,消息被传递到txCh通道。然后被goroutine txBroadcastLoop()处理, 发送给其他不知道这个交易的peer。

ProtocolManager在Start的时候,订阅TxPreEvent并启动txBroadcastLoop协程监听事件。

r6z2Qn2.png!web

当监听到事件后,调用BroadcastTx进行广播,广播按照委员及候选委员,接入节点,轻节点逐层广播。

fqae2uB.png!web

发送交易之前,会把tx.Hash放到peer的knownTxs中:

yeaAj2J.png!web

新连接建立txsyncLoop

txsyncLoop负责每个新连接的初始事务同步。 当新的peer出现时,我们转发所有当前待处理的事务。

zuiQzuq.png!web

在txsyncLoop函数中定义了一个send函数来广播交易信息:

AvmiU3z.png!web

2. 发送区块哈希值SendNewBlockHashes

广播挖矿区块 NewMinedBlockEvent

ProtocolManager在Start的时候,订阅NewMinedBlockEvent并启动 minedBroadcastLoop()协程监听事件。

meUFF33.png!web

监听到事件后,开始广播区块信息。

Afq2qaq.png!web

先根据BroadcastBlock输入的参数propagate决定是否广播区块,当propagate为true时,广播区块信息。之后开始广播区块哈希。

bmAvIfI.png!web

VJnuymi.png!web

广播时,先把hash放到knownBlocks里面,在广播区块和区块哈希

jYbUvyb.png!web

基于块通知的同步Fetcher

Fetcher Start函数中启动协程:

VjYNNr6.png!web

Fetcher模块的queue里面缓存了已经完成fetch的block,等待按照顺序插入到本地的区块链中。优先级别就是他们的区块号,这样区块数小的排在最前面。最后调用insert方法把给定的区块插入本地的区块链。

F7ZzQzN.png!web

在insert函数中,有两处广播:一是如果区块头通过验证,那么马上对区块进行广播;二是如果插入成功, 那么广播区块,第二个参数为false,那么只会对区块的hash进行广播。

FJrAJry.png!web

定时同步syncer

syncer中会定时的同BestPeer()来同步信息: 当有新的Peer增加的时候 会同步, 这个时候可能触发区块广播; 定时触发 10秒一次。

YrQnMz3.png!web

3. 发送区块内容SendNewBlock

参照SendNewBlockHashes的处理流程。

4. 发送区块头信息SendBlockHeaders

在通过握手后runPeer时,会运行protocol的run函数,接着调用startProtocols函数,进而进入NewProtocolManager的时候定义的Run,每一个SubProtocols都有一个Run。

nQZJjm6.png!web

Qz2qYjA.png!web

这个run方法首先创建了一个peer对象,然后调用了handle方法来处理这个peer。注意,这里的peer区别于p2p中的peer,但是它包含p2p的peer。

FVVvYr6.png!web

7bYjqmb.png!web

在handle最后,循环调用handleMsg, 这个方法很长,主要是处理接收到各种消息之后的应对措施。

MjuAnmI.png!web

对于GetBlockHeadersMsg的消息处理,结果调用SendBlockHeaders返回给对端:

首先解码msg,解析出getBlockHeadersData结构体。

VviqEfZ.png!web

查找方式:

从Hash指定的开始朝创世区块移动,也就是反向移动。

从Hash指定的开始正向移动。

通过Number反向查找。

通过Number正向查找。

jAZNb2r.png!web

查找结果发给对端:

YJfmeav.png!web

5. 发送区块体信息SendBlockBodies

没有用到。

6. RLP编码发送区块体信息SendBlockBodiesRLP

调用流程参考“4 发送区块头信息SendBlockHeaders”。

收到GetBlockBodiesMsg,解析msg信息,组织bodies并发送给对端。

7JNBfeE.png!web

7. 发送节点信息SendNodeData

调用流程参考“4 发送区块头信息SendBlockHeaders”。

GetNodeDataMsg对应的协议版本要大于等于eth63。

QzeMraJ.png!web

8. RLP编码发送节点信息SendReceiptsRLP

调用流程参考“4 发送区块头信息SendBlockHeaders”。

yQNZrqF.png!web

9. 请求一个区块头RequestOneHeader

调用流程参考“4 发送区块头信息SendBlockHeaders”。

2Yf6ne7.png!web

10. 通过Hash请求区块头RequestHeadersByHash

首先,在协议初始化的时候,调用protocolManager.Start

fu6Z7zz.png!web

之后启动syncer(), syncer中会定时的同BestPeer()来同步信息: 当有新的Peer增加的时候 会同步; 定时触发 10秒一次同步。

riQrqae.png!web

beyuU3r.png!web )

pm.synchronise会调用中 Downloader中的同步函数。 Synchronise试图和一个peer来同步,如果同步过程中遇到一些错误,那么会删除掉Peer。然后会被重试。

最后,在syncWithPeer中会启动几个fetcher 分别负责header,bodies,receipts处理。spawnSync给每个fetcher启动一个goroutine, 然后阻塞的等待fetcher出错。

fY7jqea.png!web

iMBvQvr.png!web

在fetchHeight中,会发出RequestHeadersByHash请求。

IJzY7jU.png!web

fetchHeaders方法用来获取header。 然后根据获取的header去获取body和receipt等信息。fetchHeaders不断的重复这样的操作,发送header请求,等待所有的返回,直到完成所有的header请求。

11. 通过Number请求区块头RequestHeadersByNumber

调用流程参考“10 通过Hash请求区块头RequestHeadersByHash”。

12. 请求区块体RequestBodies

调用流程参考“10 通过Hash请求区块头RequestHeadersByHash”。

13. 请求收据RequestReceipts

调用流程参考“10 通过Hash请求区块头RequestHeadersByHash”。

14. 请求节点信息RequestNodeData

在创建Downloader的时候,会同时启动协程 startFetcher,进而启动runStateSync。

JNZ3Ubu.png!web

ye6nUnb.png!web

vA3eim7.png!web

MVbINv7.png!web

15. 握手Handshake

head是当前的区块头,genesis是创世区块的信息,只有创世区块相同才能握手成功。如果接收到任何一个错误(发送,接收),或者是超时,那么就断开连接,握手失败。

I7vyIbv.png!web

readStatus,检查对端返回的各种情况。

qUzy6vQ.png!web

感谢HPB团队整理。

版权声明: bea2MjY.png!webmyeE32f.png!webZR7naij.png!web 作者保留权利。文章为作者独立观点,不代表巴比特立场。

发文时比特币价格 ¥45601.72

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK