# 网络 ## 概述 P2P是一种分布式网络,网络的参与者共享他们所拥有的一部分硬件资源,比如处理能力、存储能力、网络连接能力、打印机等,这些共享的资源需要由网络提供服务和内容,能被其它对等节点直接访问而无需经过中间实体。在此网络中的参与者既是服务和内容提供者,又是服务和内容获取者。 区别于传统的Client/Server中央服务器结构,P2P网络中的每个节点的地位都是对等的。每个节点在充当客户端的同时,也可以作为服务端给其它节点提供服务,极大地提高了资源的利用率。 ### 区块链网络 P2P 是区块链结构中的网络层,网络层的主要目的是实现区块链网络中节点之间的信息传播、验证和交流。区块链网络本质上是一个P2P网络,每个节点既能接受信息也能产生信息。节点之间通过维护一个共同的区块链数据来保持通信。 P2P网络作为区块链的基础,为区块链带来如下优点: * 防止单点攻击 * 高容错性 * 较好的兼容性与可扩展性 ### TRON网络 TRON架构图如下:  P2P网络作为TRON的最底层模块,直接决定了整个区块链网络的稳定性。网络模块按照功能可以划分成以下四部分: * [节点发现](#节点发现) * [节点连接](#节点连接) * [区块同步](#区块同步) * [区块和交易广播](#区块和交易广播) 下面将分别介绍这四个功能部分。 ## 节点发现 节点发现是区块链节点接入区块链网络的第一步。区块链网络是一种结构化的P2P网络。结构化网络会将所有节点按照某种结构有序的组织起来,比如形成一个环状网络或者树状的网络。 结构化网络普遍基于DHT(Distributed Hash Table,分布式哈希表) 算法实现。具体的实现方案有 Chord、Pastry、CAN、Kademlia 等算法。TRON 网络采用 Kademlia 算法。 ### Kademlia 算法 Kademlia 是分布式散列表(DHT,Distributed Hash Table)的一种实现,是去中心化 P2P 网络中最核心的一种路由寻址技术,可以在无中心服务器的情况下,在网络中快速找到目标节点。 关于算法的详细介绍请参考 [Kademlia](https://zh.m.wikipedia.org/zh-hans/Kademlia)。 ### TRON 实现 TRON 实现的 Kademlia 算法,要点如下: * 节点ID:随机产生的512bit ID * 节点距离:节点距离通过两个节点的ID异或运算得到,公式为:`节点距离 = 256 - 节点ID异或结果的前导0的个数`,如果计算结果为负数,距离等于0。 * k桶:即节点路由表。根据节点距离的远近,将远端节点划分到不同的桶中,与本节点距离相同的远端节点被记录在相同的桶中,每个桶最多容纳16个节点。根据节点距离的计算公式就可以看出,TRON 实现的 Kademlia 算法一共维护256个桶。 TRON 节点发现协议包括以下四种UDP消息: * `DISCOVER_PING` - 用于探测⼀个节点是否在线 * `DISCOVER_PONG` - 用于响应 `DISCOVER_PING` 消息 * `DISCOVER_FIND_NODE` - 用于查找与⽬标节点距离最近的其它节点 * `DISCOVER_NEIGHBORS` - 用于响应 `DISCOVER_FIND_NODE` 消息,会返回一个或者多个节点,最多16个 #### 初始化K桶 节点启动后,会读取节点配置文件中配置的种子节点,以及数据库中记录的对等节点,然后向它们发送 `DISCOVER_PING` 消息,如果收到回复 `DISCOVER_PONG` ,在K桶没满的情况下,将相应节点写入K桶;如果相应桶已经达到了16个节点,则向桶中最早的节点发起挑战,挑战成功,将被挑战节点删除,将挑战节点加入K桶。K桶初始化完成后,进行节点发现流程。  #### 发送DISCOVER_FIND_NODE消息,发现更多节点 节点发现服务会开启两个节点发现定时任务(`DiscoverTask`和`RefreshTask`),周期执行节点发现过程来更新k桶。 * `DiscoverTask` 是发现更多距离自己近的节点,每30s执行一次,执行流程如下图所示:  * `RefreshTask` 是通过随机节点ID来扩充本地k桶,即发现距离随机节点ID更近的节点,每7.2s执行一次,执行流程如下图所示:  `DiscoverTask`和`RefreshTask`中使用的节点发现算法,在一次调用中会执行8轮,每轮向K桶中距离目标节点 ID 最近的3个节点发送 `DISCOVER_FIND_NODE` 消息,并等待回复。 #### 接收到neighbors消息,更新K桶 当本节点接收到远端节点回复的 `DISCOVER_NEIGHBORS` 消息后,会依次向收到的邻居节点发送 `DISCOVER_PING` 消息,接下来如果收到了回复消息`DISCOVER_PONG`,则判断相应的K桶是否装满,如果K桶未满,则将新节点加入K桶,如果K桶满了,则向其中的某个节点发起挑战,若挑战成功(向被挑战的节点发送`DISCOVER_PING` 消息,如果未能收到其回复`DISCOVER_PONG`,则为挑战成功,否则挑战失败),则将旧节点从K桶中删除,将新节点加入K桶。  节点周期的执行节点发现任务,不断的更新K桶,构建自己的节点路由表。接下来就是节点间建立连接的过程了。 ## 节点连接 在了解节点间如何建立TCP连接之前,您需要首先了解peer节点类型。 ### peer节点管理 节点需要对peer节点进行管理分类,以进行高效稳定的节点连接。远端节点可以分为如下几类: * Active nodes:通过配置文件指定,系统启动后,会主动与其建立连接的节点。如果连接建立失败,则在每个TCP连接定时任务中都会重新尝试与其建立连接。 * Passive nodes:通过配置文件指定,被动接受连接的节点。 * Trust nodes:通过配置文件指定,Active nodes和Passive nodes都是trust nodes。当收到trust node的连接请求时,会跳过其它的条件检查,直接接受请求。 * badNodes:当收到异常的协议报文时,会将节点加入到badNodes,生效时长1个小时,当收到 badNodes 的连接请求,会直接拒绝请求 * recentlyDisconnectedNodes:当断开某条连接后,会把节点加入到recentlyDisconnectedNodes,有效时长30s,当收到 recentlyDisconnectedNodes 的连接请求,会直接拒绝请求 ### 建立节点间TCP连接 在节点启动后,会创建一个建立节点间TCP连接的定时任务`poolLoopExecutor`,用于选择节点,并与之建立连接。建立TCP连接定时任务,工作过程如下:  TCP连接主要分为两步:首先,确定要与之建立连接的节点的列表;列表中需要包含active nodes中还没有成功建立连接的节点,然后计算还需要建立连接的数量,从邻居发现节点列表里面根据 [节点过滤策略](#) 过滤出满足要求的节点,再根据 [节点打分策略](#) 对节点打分排序,将最高的相应数量的节点加入到请求列表中。最后,与请求列表中的节点建立TCP连接。 #### 节点过滤策略 建立节点连接时,需过滤掉如下几类节点,并判断节点自己的连接数是否达到了最大连接。 * 自身节点 * recentlyDisconnectedNodes列表中的节点 * badNodes列表中的节点 * 已经建立了连接的节点 * 与该节点ip建立的连接数已经达到了上限值maxConnectionsWithSameIp的节点 但是对于可信节点,会忽略掉部分过滤策略,始终与其建立连接。  #### 节点打分策略 节点分数用于确定建立连接的优先级,分数越高,优先级越高。打分维度包括: * 丢包率:丢包率越低,说明数据通信质量越好。分数与丢包率成反比,最高得分为100,最低为0 * 网络延迟:网络延时越小,说明网络质量越好。分数与平均网络延时成反比,最高得分为20,最低为0 * TCP流量:TCP流量越大,说明通信比较活跃。分数与TCP流量成正比,最高得分为20,最低为0 * 断开连接次数:断开连接次数越少,说明节点越稳定。断开连接分数为负数,且与断开连接次数为正比,值为断开连接次数的10倍 * Handshake:曾经handshake成功过的节点,表示有相同的区块链信息,因此优先选择和他们建立连接。Handshake成功次数大于0时,Handshake得分为20,否则得分为0 * 处罚状态:处于处罚状态的节点,分数为0,不参与其它维度打分,包含以下几种情况: * 节点断开连接时间不到60s * 节点在badNodes列表中 * 区块链信息不一致 计算节点分数时,首先判断节点是否为处罚状态,如果是,则分数计为0,否则,节点分数为各个维度的得分之和。 ### 握手 TCP 连接建立成功后,主动发起TCP连接请求的节点,会向邻居节点发送握手消息 `P2P_HELLO`,目的为了确认节点间的链路信息是否一致,以及是否需要发起区块同步流程。 当邻居节点收到 `P2P_HELLO` 后,会与本地信息做比较,比如检查p2p version、创世块信息是否一致,若一致,还需要检查固化快,以及判断是否是重复的连接、恶意节点等。若所有的检查条件都通过,则会回复`P2P_HELLO`消息,然后进行区块同步或广播;否则,断开连接。 ### 信道保活 信道保活是通过 `P2P_PING`、`P2P_PONG` TCP报文来完成的。当节点与邻居节点建立了TCP连接并握手成功后,节点会为连接开启一个线程 `pingTask` 定期发送 `P2P_PING` 消息以维护该TCP连接,每10s调度一次。如果在超时时间内未收到节点回复的 `P2P_PONG` 消息,则断开连接。 ## 区块同步 在与对方节点完成握手后,如果发现对方节点的区块链比本地的区块链要长,根据最长链原则,会触发区块同步的处理流程`syncService.startSync`。同步过程中的报文交互如下图:  节点A向对方节点B发送 `SYNC_BLOCK_CHAIN` 消息,以宣告本地链的摘要信息。对方节点B收到后,计算出节点A缺失的区块清单,并将缺失区块的id列表通过 `BLOCK_CHAIN_INVENTORY` 消息发送给节点A,一次最多携带2000个区块id。 节点A收到 `BLOCK_CHAIN_INVENTORY` 消息后,取出缺失区块id,并通过异步的方式向节点B发送 `FETCH_INV_DATA` 消息以请求缺少的区块,一次最多请求100个区块。如果还有需要同步的区块(即 `BLOCK_CHAIN_INVENTORY` 报文中的remain_num大于0),会触发新一轮的区块同步流程。 节点B收到节点A的 `FETCH_INV_DATA` 报文后,通过 `BLOCK` 消息将区块发送给节点A。节点A收到 `BLOCK` 报文后,异步处理该区块。 ### 链摘要及缺失区块清单 下面根据几个不同的区块同步场景示例,说明链摘要和节点缺少的区块清单的生成。 * 链摘要:有序的区块blockID列表,包括:最高固化块、最高非固化块,以及之间二分法对应的区块 * 缺失区块清单:邻居节点根据链摘要与自己链进行比较,确定对方缺失的区块清单,返回一组连续的区块blockID以及剩余的块数 #### 普通场景 本地头块高度为1018,固化块高度为1000,两个节点刚建立连接,所以共同块高度为0,通过二分方式获取到节点A的本地链摘要为:1000、1010、1015、1017、1018。 节点B收到节点A的链摘要后,结合本地链可以生产节点A缺少的区块清单为:1018、1019、1020、1021。之后节点A根据缺失区块清单,请求同步区块1019、1020、1021。  #### 切链场景 本地主链头块高度为1018,固化块高度为1000,两个节点刚建立连接,所以共同块高度为0,通过二分方式获取到节点A的本地链摘要为:1000、1010、1015、1017、1018。 节点B收到节点A的链摘要后,发现本地主链跟节点A的主链不在同一条链上,对比节点A的链摘要,找到共同块高度为1015,则认为节点A缺少的区块清单为:1015、1016'、1017'、1018',1019'。之后节点A根据缺失区块清单,请求同步区块1018',1019'。  另一种切链场景,本地主链头块高度为1018,固化块高度为1000,共同块为1017‘,位于fork链上,通过二分方式获取到节点A的本地链摘要为:1000、1009、1014、1016'、1017'。 节点B收到节点A的链摘要后,结合本地链可以生产节点A缺少的区块清单为:1017'、1018'、1019'。之后节点A根据缺失区块清单,请求同步区块1018',1019'。  ## 区块和交易广播 当超级代表节点生产出新的区块,或者全节点接收到用户发起的新交易时,会发起交易&区块广播流程。当节点接收到新区块或者新交易时,会转发相应的区块或交易,转发与广播的流程一样。报文交互如下图所示:  其中涉及到的消息类型包括: * INVENTORY - 广播清单:区块或者交易id列表 * FETCH_INV_DATA - 请求需要获取的清单数据:区块或者交易id列表 * BLOCK - 区块数据 * TRXS - 交易数据 节点A将待广播交易或区块通过 `INVENTORY` 清单消息发送到节点B。节点B收到 `INVENTORY` 清单消息后,需要检查对方节点的状态,如果可以接收该消息,则将清单中的区块/交易放入待获取队列 `invToFetch` 中。如果是区块清单,还会立即触发"获取区块&交易任务",来向节点A发送 `FETCH_INV_DATA` 消息获取区块&交易。 节点A收到 `FETCH_INV_DATA` 消息后,会检查是否发送过清单消息给对方,如果发送过,则根据清单数据,向节点B发送交易或者区块消息。节点B收到交易或者区块消息后,处理消息,并触发转发流程。 ## 总结 本文介绍了TRON最底层模块-P2P网络相关的实现细节,包括节点发现、节点连接、区块同步、区块和交易广播流程,希望通过阅读本文能帮助开发者进一步了解和开发java-tron网络相关模块。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up