---
# System prepended metadata

title: Framework模块

---

# Framework模块

P2P网络
* 节点发现 - 发现对等节点，构建路由表
* 节点连接 - 选择可信的节点，建立连接


## 节点发现
区块链网络是一种结构化的P2P网络。结构化网络会将所有节点按照某种结构有序的组织起来，比如形成一个环状网络或者树状的网络。 结构化网络的具体实现上普遍都是基于DHT(Distributed Hash Table，分布式散列表) 算法。具体的实现方案有 Chord、Pastry、CAN、Kademlia 等算法。Tron 采用 Kademlia 节点发现算法。

Kademlia 是分布式散列表的一种实现，是去中心化 P2P 网络中最核心的一种路由寻址技术，可以在无中心服务器的情况下，在网络中快速找到目标节点。

* 节点ID
    节点 ID 为节点的标识符，占64字节，即512bit。节点在加入TRON网络时，会随机分配一个节点 ID。
* 节点距离
    节点距离通过两个节点的ID异或运算得到，公式为`distance(node1, node2) = 256 - 节点ID异或结果的前导0的个数`，如果计算结果为负数，距离等于0。
* K桶
    Kademlia 的路由表是通过一些称之为 K 桶的链表构造起来的。根据节点距离的远近，将远端节点划分到不同的桶中，与本节点距离相同的远端节点被记录在相同的桶中，每个桶最多容纳16个节点。根据节点距离的计算公式就可以看出，Tron 实现的 Kademlia 算法一共维护256个桶。

Kademlia 协议包括四种UDP消息：Ping，Pong，FindNode，Neighbors。
* ping - 用于探测⼀个节点是否在线
* pong - 用于响应ping消息
* findnode - 用于查找与⽬标节点距离最近的其它节点
* neighbors - 用于响应findnode消息，会返回一个或者多个节点，最多16个
 
以上四个消息，就可以实现新节点的加入、K-桶的更新等机制。
 
节点发现过程中的报文交互：
![](https://i.imgur.com/3OIRBZA.png)
### K桶更新
节点发现是区块链节点接入区块链P2P网络的第一步。节点发现实际上就是进行 K 桶更新，将发现的远端节点加入到 K 桶中。

#### 初始化K桶
节点启动后，会读取节点配置文件中配置的种子节点，以及数据库中记录的对等节点，然后向它们发送Ping消息，如果收到回复Pong，在K桶没满的情况下，将相应节点写入K桶；如果相应桶已经达到了16个节点，则向桶中最早的节点发起挑战，挑战成功，将被挑战节点删除，将挑战节点加入K桶。

#### 发现更多节点 - 即发送findnode消息

节点启动时，会开启两个定时节点发现任务（`DiscoverTask`和`RefreshTask`），周期执行节点发现过程来更新k桶。

* `DiscoverTask` 是发现更多距离自己近的节点，每30s执行一次，执行流程如下图所示：（图需要改）

    ![](https://i.imgur.com/sOLYBFZ.png)

* `RefreshTask` 是通过随机节点ID来扩充本地k桶，每7.2s执行一次，执行流程如下图所示：（图需要改）

    ![](https://i.imgur.com/E03hwBB.png)


`DiscoverTask`和`RefreshTask`中使用的节点发现算法，在一次调用中会执行8轮，每轮向3个节点发送 `FindNode` 消息，执行过程如下：

1. 从K桶中找到距离目标节点 ID 最近的最多16个节点
2. 遍历节点，判断节点是否发送过 FindNode 消息，若是则跳过，若否，则发送 FindNode 消息，并等待50ms
3. 若遍历完列表或者已经向3个节点发送过 FindNode 消息，则退出此轮循环
4. 若此轮中，没有找到一个可以发送 FindNode 消息的节点，则退出调度


#### 接收neighbors消息，更新K桶
接收到neighbors消息后，依次向收到的邻居节点发送 `sendPing()` 消息，如果收到回复消息pong，则判断相应的K桶是否装满，如果K桶未满，则将新节点加入K桶，如果K桶满了，则向其中的某个节点发起挑战，挑战成功，则将旧节点从K桶中删除，将新节点加入K桶。

![](https://i.imgur.com/hl5GJyT.png)



### 节点发现过程中节点的状态变更
可以根据节点所处的情形比如未连接，已在K桶中等，对节点进行状态划分。节点的状态包括：
* Discovered - 新发现的节点
* Dead - 对其第一次发送ping消息就没有在规定时间内回复的节点
* Alive - 回复了pong消息的节点，并等待被加入到K桶中
* Active - 在K桶中的节点
* EvictCandidate - 之前在K桶中的节点，但是由于被挑战，而从K桶中移出的节点
* NonActive - 回复了pong消息的节点，但是由于K桶满了，还未挑战成功的节点

在节点发现过程中，节点的状态转换图如下：
![](https://i.imgur.com/asFHB9I.png)




## 节点连接
在节点启动后，会创建一个建立TCP连接的定时任务`poolLoopExecutor`，与节点建立连接的过程如下：

1. 选择要与之建立连接的节点
    * 把还没有建立连接的activeNodes加入请求列表connectNodes中
    * 计算还需选取的节点个数`lackSize`
    * 如果lackSize大于0，选择节点。首先从邻居发现节点列表里面过滤出满足要求的节点，然后根据[节点打分策略](#)对节点打分，将最高的lackSize个节点加入connectNodes
2. 与请求列表中的节点建立TCP连接

### 节点打分策略
打分维度包括：

* 丢包率：丢包率越低，说明数据通信质量越好。分数与丢包率成反比，最高得分为100，最低为0
* 网络延迟：网络延时越小，说明网络质量越好。分数与平均网络延时成反比，最高得分为20，最低为0
* TCP流量：TCP流量越大，说明通信比较活跃。分数与TCP流量成正比，最高得分为20，最低为0
* 断开连接次数：断开连接次数越少，说明节点越稳定。断开连接分数为负数，且与断开连接次数为正比，值为断开连接次数的10倍
* Handshake：曾经handshake成功过的节点，表示有相同的区块链信息，因此优先选择和他们建立连接。Handshake成功次数大于0时，Handshake得分为20，否则得分为0
* 分数处罚：处于处罚状态的节点，分数为0，不参与其它维度打分，包含以下几种情况：
    * 节点断开连接时间不到60s
    * 节点在badNodes列表中（badNodes：当收到异常的协议报文时，会将节点加入到badNodes，生效时长1个小时，当收到 badNodes 的连接请求，会直接拒绝请求）
    * 区块链信息不一致

计算节点分数时，首先判断节点是否为处罚状态，如果是，则分数计为0，否则，节点分数为各个维度的得分之和。


### 握手
握手相关的TCP报文：HelloMessage。
TCP 连接建立成功后，主动发起TCP连接请求的节点，会向邻居节点发起握手过程，确认节点间的链路信息是否一致，以及是否需要发起区块同步。握手消息格式如下：
```
message HelloMessage {
  message BlockId {
    bytes hash = 1;
    int64 number = 2;
  }
  Endpoint from = 1;
  int32 version = 2; //p2p版本信息
  int64 timestamp = 3; //消息时间戳
  BlockId genesisBlockId = 4; //创世块id
  BlockId solidBlockId = 5; //固化块id
  BlockId headBlockId = 6; //头块id
  bytes address = 7; //节点地址
  bytes signature = 8; //消息签名
  int32 nodeType = 9; //节点类型  0全节点   1轻节点
  int64 lowestBlockNum = 10; //最低块高度  全节点-0  轻节点-最低块
}
```
当收到 Hello message 后，会进行如下检查

1. 本地头块高度是否小于握手消息中的最低块高度，是则断开连接
2. p2p version 检查，若版本号不一致，则断开连接
3. 创世块检查，若创世块不一致，则断开连接
4. 固化块检查，若本地区块链的固化块高度大于邻居节点的固化块高度，且邻居节点的固化块不在区块链中，则断开连接
5. 判断是否为可信节点（trustNodes），若不是可信节点，需要进一步检查，若满足以下任何一种情况，则拒绝请求
    a. 已经达到了最大连接数
    b. 节点在recentlyDisconnectedNodes列表中
    c. 节点在badNodes列表中
    d. 此ip已经建立了maxNodesWithSameIp个连接
6. 判断是否为重复连接，若是则断开后面建立的那条连接，通过节点发起连接请求的时间来判断
7. 如果本地节点为中心转发节点，则判断对方节点是否为非SR节点，是则断开连接；或者是SR节点但消息签名验证失败也会断开连接

### 信道保活
信道保活是通过ping、pong TCP报文来完成的，与节点建立TCP连接并握手成功后，系统会为连接开启一个线程pingTask定期发送PING_MESSAGE消息，每10s调度一次。如果在超时时间内未收到节点回复的PONG_MESSAGE消息，则断开连接。

