# 理解以太坊的 P2P 网络

By [DK](https://paragraph.com/@iamdk) · 2022-05-11

---

此文旨在帮助你理解 P2P 网络，并阐述一些以太坊的实现细节。P2P 技术利用终端设备的丰富资源，能够缓解中心化系统的短板，而且从九十年代开始，这项技术就已经被 eMule，bitTorrent 和 Skype 等知名软件所采用。P2P 技术也是比特币或者以太坊区块链系统的核心组件（我们的项目 Shyft Network 就是从这些区块链中衍生出来的）。很多人都听说过 P2P，但是不知道它到底是什么。那就让我们从了解什么是 P2P 网络开始吧。

P2P网络
-----

点对点（P2P）网络是一种**网络覆盖层**（overlay network）——就是说，它是建立在公开互联网之上的。从数学的角度来说，P2P 网络可以被视作一个有向图 G = (V,E)，其中 V 是网络中的对等节点集合，**E** 是对等节点所连成的边的集合（也即节点间连接的集合）。每个对等节点 p 都有一个独一无二的标识号 pid。集合 E 中的边（p，q）指 p 可通过直接相连的路径向 q 发送消息；也就是说，p 使用 q 的 pid 作为目的地址，在网络之上向 q 发送消息。尽管在底层的 TCP/IP 网络中，相似的 IP 地址可以转译为在地理位置上相互接近，但很少有这么明确的直接关联。

理想情况下，所有的对等节点间都应该有一条路径相连。但因为每个节点对网络拓扑和其他对等节点只有一个不完整的视图，所以网络覆盖层需要中间节点将消息转发至正确目的。图的结构为每对节点提供了多条中间路径，因此就算对等节点改变，也可通过图的连通性提供网络的**恢复能力**。对每个对等节点来说，图的连通性通过与其他对等节点的邻接关系来反映。当对等节点加入或者离开网络，邻接的对等节点可能会持有不正确的邻接信息。因此使用**网络覆盖层维护机制**（Overlay maintenance mechanisms）保存更新的邻接信息，使得所有节点间保持连通性。

P2P 网络中的参与者向其他网络参与者提供部分资源。不需要中心化的协调者，每个对等节点都可贡献计算周期（CPU），磁盘存储和网络带宽。传统的“客户端-服务器”模型中，服务器提供资源，客户端使用资源；与之相对的，在 P2P 网络中，对等节点既是网络资源的供应者，也是消费者。因此，P2P 网络可以很好地解决 客户端-服务器 模型下的一些短板，比如**可扩展性**和**单点故障**。

一般来说，P2P 网络会有一个门槛，节点的资源贡献高于这个门槛才能加入网络。度量资源贡献的标准应该是公平的，比如说，要求网络中每个对等节点的平均贡献应该在 P2P 系统总体平均值的统计范围内等。资源贡献应该是双方互惠的。付出贡献后可得到的利益，吸引着用户加入 P2P 应用。

以太坊的 P2P 网络是如何工作的？
------------------

以太坊的官方客户端节点软件 **Geth**，基于一种覆盖层维护机制 （称作 Kademlia 分布式哈希表）实现了对等节点发现协议（RLPx 节点发现协议）。虽然 Kademlia 是为了在 P2P 网络中有效地定位和存储内容而设计的，以太坊的 P2P 网络只用它来发现新的对等节点。

### Kademlia

以太坊网络中，每个客户端节点都配备有一个 **enode** ID，之后将此 ID 用 SHA3 算法**散列**为一个 256 位的值。Kademlia 使用 XOR 操作定义距离，因此两个 256 位的数字之间的距离是他们的按位异或值（bitwise exclusive OR）。每个对等节点都拥有一个包含 256 个不同的桶（buckets）的数据结构，每个桶 i 中存储与本节点距离在 2i-1 到 ​2i 之间的 16 个节点。为了发现一个新的对等节点，以太坊节点选择自己作为目标 x，从桶中寻找到 16 个与目标 x 最近的节点，之后请求这 16 个节点，让它们从自己的桶中各找出 16 个与目标 x “更近” 的节点并返回，这样以来，会得到至多 16x16 个新发现的节点。之后请求这 16x16 个新发现的节点中离目标 x 最近的 16 个节点，让它们返回与 x 更近的 16 个节点。这个过程持续迭代，直到没有新节点被发现。

\-异或操作示意图-

\-bucket 与距离对应的示意图-

### 对等节点间通信

Geth 使用 UDP 连接交换 P2P 网络的信息。有 4 种类型的 UDP 消息。一条 _\*\*ping\*_\* 消息请求一条 _\*\*pong\*_\* 消息作为返回。此对消息用来判断相邻节点是否可响应。一条 _\*\*findnode\*_\* 消息请求一条 _\*\*neighbors\*_\* 消息（其中包含 16 个已经被响应节点知晓的节点列表）作为返回。当建立好对等节点的连接之后，Geth 节点通过加密和认证的 TCP 连接来交换区块链信息。

### 数据结构

Geth 客户端用两种数据结构存储其他节点的信息。第一种是称作 _db_ 的长期数据库，它存储在磁盘内，客户端重启之后数据也是持久存在的。_db_ 中包含客户端交互过的每个节点信息。db 的每条记录包含节点 ID，IP 地址，TCP 端口，UDP 端口，（此客户端）最后一次向（记录中）节点发送 ping 的时间，最后一次从节点收到 pong 的时间，节点响应 _findnode_ 消息的失败次数。如果最后一次从一个节点收到 _pong_ 消息的时间超过了一天，此节点将会被移出 db。

第二种数据结构是称作 _table_ 的短期数据库。当客户端重启时 _table_ 是空的。_table_ 包含 256 个桶，每个桶存储至多 16 条记录。每条记录存储其他以太坊节点的信息——节点的 ID，IP 地址， TCP 端口和 UDP 端口。如果记录中的某个节点对于 _findnode_ 消息连续响应失败，多于 4 次时将被移出 table。

当某个客户端第一次启动时，它的 db 是空的，只知道 6 个硬编码的引导节点。随后，当客户端开始发现对等节点，客户端依据上面描述的机制，将节点加入 db 和 table。

---

*Originally published on [DK](https://paragraph.com/@iamdk/p2p)*
