比特币的P2P网络层源码分析

在任意给定时刻,一个节点总是连接到多个其他节点。默认情况下,一个节点连接到8个其他节点(链出),并允许多达125个链入节点连接进来。 相关常量定义在`net.h`文件中。

attachments-2018-03-9MDlTgOS5aa74ab46b850.png

作者:瀛渠

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

喜欢的看原稿吧。这个编辑器实在不会用!!!!原稿链接

个人博客

# 0x1 数据结构

在任意给定时刻,一个节点总是连接到多个其他节点。默认情况下,一个节点连接到8个其他节点(链出),并允许多达125个链入节点连接进来。

相关常量定义在net.h文件中。

节点集合则由全局变量vNodes维护。

CNode用于表示一个节点。

CNode包含许多属性,其中大部分属性都与底层链路(如套接字,字节流等)有关。

CNode的关键属性如下:

* nServices: 通常被称为“服务位”。这是一个bitmap数据结构,用于表示peer提供的服务种类。具体种类如下。

    * NODE_NONE: 初值

    * NODE_NETWORK: 全节点。SPV节点和其他类型的轻节点不设置该标识。

    * NODE_GETUTXO: 该节点能够响应getutxo协议请求。Bitcoin Core节点不支持此功能。此功能由一个由Bitcoin XT支持。[BIP64](https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki)

    * NODE_BLOOM: 

    * NODE_WITNESS: 表示可以向节点询问块和交易,包括见证数据。应该是只支持隔离见证。

    * NODE_XTHIN: 表示节点支持Xtreme Thinblocks

    * NODE_NETWORK_LIMITED: 表示节点仅支持最近的288年区块(即两天的数据)[BIP159](https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki)

* fClient: 表示节点是否是SPV节点

* fWhiteListed: 是否是白名单节点。如果是的话,该节点不会因为`坏`的行为被屏避掉。

* vSendMsg: 队列中待发送的消息队列

* vRecvMsg: 我们从Peer接收到的消息队列

#0x2 节点发现和节点连接

## 地址管理

地址管理器用于管理节点的IP地址和端口。(见`addrman.h`)

设计目标:

* 将地址表保存在内存中,并将整个表异步转储到peers.dat。

* 确保没有(本地化的)攻击者可以用它的节点/地址填充整个表格。

为此:

* 地址以Bucket的方式组织,即地址被放到桶里。

    * 尚未尝试过的地址进入1024个“new”桶。

    * 已知可访问的节点地址进入256个“tried”桶。

        * 每个地址范围随机选择8个桶

        * 根据完整地址从实际存储桶中选择实际存储桶。

        * 当为一个完整的桶添加一个新的好地址时,一个随机选择的entry(偏向于最近尝试过的)会被弹出,回到“新”桶。

    * 分组选择基于密码散列,使用随机生成的256位密钥,这不应该被攻击者观察到。

    * 多个索引用于提升性能。

** 时间戳 **

地址管理器也要持续跟踪每个节点的最近活跃情况。时间戳仅在一个地址上更新,并且在时间戳超过20分钟时保存到数据库。 通过理解时间戳的作用,将更加清楚的理解为什么要保持时间戳。

## 节点发现

节点发现从链路层来讲,就是发现一个节点的IP地址和端口号。有多种方式:

* 地址数据库,即peers.dat文件

    * 在节点启动时读入并加载到节点管理器中。该方法在节点首次运行时是不支持的。因为peers.dat文件还不存在。 

* 用户指定(--addnode, --connect)

* DNS种子(DNS seeding)

    * 仅当peers.dat文件为空时,DNS种子才会被使用。

    * 默认DNS种子有6个:seed.bitcoin.sipa.be, dnsseed.bluematt.me, dnsseed.bitcoin.dashjr.org, seed.bitcoinstats.com, seed.bitcoin.jonasschnelli.ch, seed.btc.petertodd.org

* 硬编码的种子

    * 如果DNS seeding失败,客户端使用硬编码的种子。[chainparamsseeds.h] 这些地址仅用作最后的手段。理想情况是尽快远离种子节点,以避免过载这些节点。DNS种子和硬编码的种子的时间戳均为0,这样可以避免此类地址被广播到网络中或响应`getaddr`。

* 从其他Peer获取(`getaddr`和`addr`消息)

    * 节点间通过`getaddr`和`addr`交换IP地址信息。

    * 但是,“addr”消息也可能会不请自来,因为节点在以下情况时无偿地发布地址:

        * 当节点转发地址时

        * 周期性广播节点自己的地址(每24小时)

        * 当连接建立时(响应`version`消息时带回)

    * 节点发送`getaddr`消息的时机

        * 在响应`version`消息,并且节点自身的地址量小于1000时。

    * 当接收到`addr`时:

        * 发送消息的节点版本太老,并且我们已经有1000个节点地址了,该消息被忽略。

        * 如果节点是当前版本,并且尝试向我们发送超过1000个节点地址,则该节点会被惩罚。

        * 如果该地址24小时内已经发现,并且当前时间戳超过60分种,则时间戳更新为60分种。

        * 如果该地址24小时内没有出现过,而时间戳是24小时之前,则更新为24小时。

    * 当响应`getaddr`消息时:

        * 该节点计算出在过去3个小时内有多少个地址有时间戳。

        * 它发送这些地址,但如果有超过2500个地址,它随机选择2500。

        * 它清除我们认为远程节点所拥有的地址列表,这将触发发送到节点的刷新。 请参阅SendMessages。

    * 地址中继:

        * 一旦新地址被添加

            * 地址的时间戳在10分钟之内

            * `addr`消息包括10个地址或少一些

            * fGetAddr==false

            * 该地址是可路由的

            * 满足以上条件,则新地址会被随机的发送出去

## 节点连接

节点链接由线程ThreadOpenConnections管理。它负责选择可用的地址,建立链接并在适当的时候释放链接。

而inbound链接则由ThreadSocketHandler负责处理[ThreadMessageHandler]。

对于inbound链接,系统通过select执行IO操作。所以,其支持的inbound数量不会太多,目前,是125个。

# 插口(Sockets)和消息


## Socket线程 (net.cpp)


## 消息线程


## ProcessMessages (net_processing.cpp)


ProcessMessages是net_processing.cpp中的处理和验证交易和区块等相关代码代码的入口点,同时它也处理getaddr, addr等比特币协议相关的消息。该方法主要完成消息的复制并调用ProcessMessage处理消息。


ProcessMessage基本上是一个大型的“开关”,它根据消息类型[NetMsgType]来采取行动。


消息类型列表如下[protocol.cpp]:


```

namespace NetMsgType {

const char *VERSION="version";

const char *VERACK="verack";

const char *ADDR="addr";

const char *INV="inv";

const char *GETDATA="getdata";

const char *MERKLEBLOCK="merkleblock";

const char *GETBLOCKS="getblocks";

const char *GETHEADERS="getheaders";

const char *TX="tx";

const char *HEADERS="headers";

const char *BLOCK="block";

const char *GETADDR="getaddr";

const char *MEMPOOL="mempool";

const char *PING="ping";

const char *PONG="pong";

const char *NOTFOUND="notfound";

const char *FILTERLOAD="filterload";

const char *FILTERADD="filteradd";

const char *FILTERCLEAR="filterclear";

const char *REJECT="reject";

const char *SENDHEADERS="sendheaders";

const char *FEEFILTER="feefilter";

const char *SENDCMPCT="sendcmpct";

const char *CMPCTBLOCK="cmpctblock";

const char *GETBLOCKTXN="getblocktxn";

const char *BLOCKTXN="blocktxn";

}

```


## SendMessages (main.cpp)

SendMessages创建消息并在peer的vSendMsg队列(双端队列或C ++中的“deque”)中对其进行排队。vSendMsg对象基本上只是序列化的数据。

# Locks

P2P层主要的锁有:

* cs_vNodes: 控制对CNode对象的访问

* cs_vSend: 控制对节点的发送缓存的访问

* cs_vRecvMsg: 控制对节点的接收缓存的访问

* cs_inventory

# 拒绝服务的防范措施

一旦发现异常行为的节点,则直接ban掉。

DoS预防框架在2011年引入。具体见:https://github.com/bitcoin/bitcoin/pull/517

** 被禁节点 **

被禁节点存储在setBanned中。setBanned是map类型的数据结构,定义为`typedef std::map banmap_t`。

默认情况下,一个节点被禁止24小时,但可以使用-bantime选项进行配置。

文章发布只为分享区块链技术内容,版权归原作者所有,观点仅代表作者本人,绝不代表区块链兄弟赞同其观点或证实其描述。

attachments-2018-02-kL1zBfXx5a7ffd0b78798.jpg

  • 发表于 2018-05-24 23:17
  • 阅读 ( 1757 )
  • 分类:比特币

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
不写代码的码农
瀛渠

互联网

3 篇文章

作家榜 »

  1. amconykx 167179 文章
  2. iwp41863 27561 文章
  3. 社区运营-小以 548 文章
  4. 社区运营-小链 244 文章
  5. 于中阳Mercina-zy 79 文章
  6. 涂晶 75 文章
  7. 李晓琼 44 文章
  8. 兄弟连区块链培训 42 文章