以太坊源码分析之 P2P网络(二、节点发现流程)

P2P网络及节点发现机制 1 分布式网络介绍 1.1 Kad网介绍 1.2 Kad网络节点距离 1.3 K桶 1.4 Kad通信协议 2 邻居节点 2.1 NodeTable类主要成员 2.2 邻居节点发现方法 2.3 邻居节点网络拓扑及刷新机制。   1 分布式网络介绍 以太坊底层分布式网络即P2P网络,使用了经典的Kademlia网络,简称kad。 1.1 Kad网介绍 Kademlia在20... 阅读详情

区块链特辑 :https://blog.youkuaiyun.com/fusan2004/article/details/80879343,欢迎查阅,原创作品,转载请标明!

上一篇文章简单介绍了下一些基础的类型定义,从这一篇开始我们将描述p2p网络的更多细节。从关于节点的定义来看,其实不同定义是有不同含义的,Node代表的是一个孤立的节点,这个节点不代表我们和他会建立连接,而Peer是肯定会去连接的,但是不代表一定会建立出连接,只有建立连接以后才会生成session,在session上才进行了以太坊的数据的交换。

对于了解p2p系统的人来说,肯定对区块链p2p底层有一种疑惑,为什么呢?因为在中心化的p2p网络中,会有一个server用来搜集peer信息,这样在数据交互过程中,每个peer一般情况下是先通过这个server拿到一定数量的peer列表,然后挨个去建立连接,最后进行数据交互。但众所周知的是,区块链是一个去中心化的系统,这种server的存在将会彻底破坏区块链可信任的基础,那么以太坊是如何解决节点获取问题的呢?答案就是Kademlia算法,这是一种分布式存储及路由的算法,能够保证经过最多n步后找到需要的数据,具体的算法可以参考 https://www.jianshu.com/p/f2c31e632f1d 这篇文章,比较通俗易懂。

在这里我们更加关注以太坊中关于节点发现的实现,这部分的逻辑都是在NodeTable中,我们先来看下NodeTable类的成员变量和函数,然后再根据代码逻辑详细说明整个流程,后续如果有时间我会再补个流程图。

NodeTable类

// NodeTable类负责以太坊p2p网络底层节点发现的所有管理
// 节点发现是通过udp来完成,因此这里继承了UDPSocketEvents,来响应一些事件
class NodeTable: UDPSocketEvents, public std::enable_shared_from_this<NodeTable>
{
    friend std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable);
    using NodeSocket = UDPSocket<NodeTable, 1280>;              // UDPSocket,这是在UDP.h中定义,1280表示的是最大数据报大小
    using TimePoint = std::chrono::steady_clock::time_point;    // < Steady time point.
    using NodeIdTimePoint = std::pair<NodeID, TimePoint>;
    struct EvictionTimeout                                      // 用于记录淘汰的节点的timepoint,以及用于替代他的新节点id
    { 
        NodeID newNodeID;
        TimePoint evictedTimePoint;
    };

public:
    enum NodeRelation { Unknown = 0, Known };   // 判断节点的关系,在部分函数参数中需要
    enum DiscoverType { Random = 0 }; 
    NodeTable(ba::io_service& _io, KeyPair const& _alias, NodeIPEndpoint const& _endpoint, bool _enabled = true);   //构造函数需要一个用于io的host,证书以及要监听的ip地址和端口
    ~NodeTable();
    //返回两个nodeid基于异或计算的距离,这就是NodeEntry中的distance,也是判断两个节点逻辑上“距离”的计算方法,可不用关注细节
    static int distance(NodeID const& _a, NodeID const& _b) { u256 d = sha3(_a) ^ sha3(_b); unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; }
    void setEventHandler(NodeTableEventHandler* _handler) { m_nodeEventHandler.reset(_handler); }   //为NodeEntryAdded和NodeEntryDropped事件设置事件句柄,实际上这两个事件都会在上层被处理,这里暂不关注
    void processEvents();                      // 这个函数也是在上层被调用的,这样上层就可以来处理setEventHandler设置的事件了
    std::shared_ptr<NodeEntry> addNode(Node const& _node, NodeRelation _relation = NodeRelation::Unknown);  //添加节点,这部分内容较多,会在后面流程介绍细说
    std::list<NodeID> nodes() const;           // 返回node table中活跃的node id的列表
    unsigned count() const { return m_nodes.size(); }  // 返回节点数量
    std::list<NodeEntry> snapshot() const;             //返回节点快照,这里可以发现关注的都是NodeEntry,这是因为node table需要关心distance
    bool haveNode(NodeID const& _id) { Guard l(x_nodes); return m_nodes.count(_id) > 0; }  // 判断节点是否已经存在
    Node node(NodeID const& _id);              // 返回该node id对应的node,如果不存在返回空节点
    // 下面就是Kademlia算法需要配置的一些常量
    static unsigned const s_addressByteSize = h256::size;                   // < Size of address type in bytes. 32位
    static unsigned const s_bits = 8 * s_addressByteSize;                   // < Denoted by n in [Kademlia].256个bit
    static unsigned const s_bins = s_bits - 1;                              // < Size of m_state (excludes root, which is us). 255个槽位
    static unsigned const s_maxSteps = boost::static_log2<s_bits>::value;   // < Max iterations of discovery. (discover), discovery的最大迭代次数,n取log
    // 可选的参数
    static unsigned const s_bucketSize = 16;            // < Denoted by k in [Kademlia]. Number of nodes stored in each bucket. 每一个bucket保存的node数
    static unsigned const s_alpha = 3;                  // < Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests. findNode请求的并发数
    // 一些定时器间隔
    std::chrono::milliseconds const c_evictionCheckInterval = std::chrono::milliseconds(75);      // 淘汰超时检测的间隔
    std::chrono::milliseconds const c_reqTimeout = std::chrono::milliseconds(300);                // 每个请求的等待时间
    std::chrono::milliseconds const c_bucketRefresh = std::chrono::milliseconds(7200);            // 更新bucket的时间,避免node数据变得老旧
    struct NodeBucket   //槽位,每个不同的distance都会包含若干个节点,最多不超过上面的s_bucketSize,也就是16个
    {
        unsigned distance;
        std::list<std::weak_ptr<NodeEntry>> nodes;
    };
    void ping(NodeIPEndpoint _to) const;     // ping, 连接某个端点
    void ping(NodeEntry* _n) const;          // 用来ping已知节点,这是node table在更新buckets或者淘汰过程中调用
    NodeEntry center() const { return NodeEntry(m_node.id, m_node.publicKey(), m_node.endpoint); }
    std::shared_ptr<NodeEntry> nodeEntry(NodeID _id);
    void doDiscover(NodeID _target, unsigned _round = 0, std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>> _tried =  std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>>());    // 用于发现给定目标距离近的节点
    std::vector<std::shared_ptr<NodeEntry>> nearestNodeEntries(NodeID _target);          //返回距离target最近的节点列表
    void evict(std::shared_ptr<NodeEntry> _leastSeen, std::shared_ptr<NodeEntry> _new);  // 异步丢弃不响应的_leastSeen节点,并添加_new节点,否则丢弃_new
    void noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint);        //为了维持节点table,无论何时从一个节点获取到activity,都会调用这个noteActiveNode
    void dropNode(std::shared_ptr<NodeEntry> _n);     //当超时出现后,调用
    NodeBucket& bucket_UNSAFE(NodeEntry const* _n);   //这是返回bucket的引用,后面可以看到,这是唯一添加node到bucket的入口
    void onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet); //当m_socket收到数据包,调用该函数,这是继承的UDPSocketEvents里函数
    void onDisconnected(UDPSocketFace*) {}            //当socket端口后调用,也是继承的UDPSocketEvents里函数
    void doCheckEvictions();                          // 被evict调用确认淘汰检查被调度,并且在没有淘汰剩余时停止,异步操作
    void doDiscovery();                               // 在c_bucketRefresh间隔内查询随机node
    std::unique_ptr<NodeTableEventHandler> m_nodeEventHandler;      // < Event handler for node events. node事件的事件句柄
    Node m_node;                                                    // < This node. LOCK x_state if endpoint access or mutation is required. Do not modify id. 当前自己这个节点
    Secret m_secret;                                                // < This nodes secret key. 当前节点的私钥
    mutable Mutex x_nodes;                                &nb
以太坊源码分析之 P2P网络(三、UDP底层通信) 区块链特辑 :https://blog.youkuaiyun.com/fusan2004/article/details/80879343,欢迎查阅,原创作品,转载请标明!这周工作有点小忙,部门区块链基础平台的开发开始进入节奏了,和上一篇间隔间隔有点久了,以后还是要坚持,不能刚开始就犯毛病了。上篇讲的是以太坊p2p网络的一个重点部分——节点发现,在介绍的时候提过,节点发现是通过udp的方式来进行的,这一篇就介... 阅读详情

相关推荐

深入理解以太坊 P2P 网络设计

前言 在设计公链时,节点与节点之间建立连接需要 P2P 协议,从而实现数据的同步,于此同时上层应用还需要封装一些通信逻辑,比如节点之间的区块同步、交易数据同步等。 本篇文章将对 P2P 网络发展进行简单概述,同时将从源码角度对以太坊中的节点发现机制、分布式哈希表、节点查找、节点新增、节点移除等进行简单介绍,并对其 P2P 网络安全性设计进行简要分析。 基础知识 P2P网络 P2P 网络不同于传统的 CS 结构,在 P2P 网络中每个节点既可以是客户端也可以是服务端,节点之间的通信协议一般直接通过 Socke

SierraW的博客 1291

以太坊源码分析之 P2P网络(一、基础类或类型描述)

从这篇开始,我们将会围绕着以太坊和eos的源码对一个区块链平台系统的不同组成展开进一步的源码级分析,之所以选择P2P开始我们的征程,主要有两个原因,一是我们部门目前正在进行内部区块链平台的开发,我本人正好负责了其中p2p及网络部分的研发任务,二是p2p作为区块链底层通信的重要组成部分,对于无论公链还是联盟链都有着极其重要的作用,但是p2p网络和其他组件的耦合度恰恰是最少的,从p2p网络开始,有利于...

fusan2004的博客 1152

以太坊源码分析之 P2P网络(六、p2p连接控制与消息处理(下))

区块链特辑 :https://blog.youkuaiyun.com/fusan2004/article/details/80879343,欢迎查阅,原创作品,转载请标明! 这是p2p网络系列文章的最后一篇,前面很多篇主要都是在描述p2p的底层实现,那么p2p在整个系统中处于什么位置和提供什么功能,都将会在本篇进行一部分的总结,首先我们看下host相关代码,来了解p2p的运行模式,然后再去了解capacit...

fusan2004的博客 1166

以太坊源码之P2P网络及节点发现机制

本书用来描述以太坊网络发现的实现原理。很适合相关从事和学习以太坊技术的同学。

EV1527 vs HS1527:固定码芯片的STM32解码避坑指南

本文深入解析EV1527与HS1527固定码芯片的技术差异,提供基于STM32的完整解码方案与稳定性优化策略。通过硬件接口配置、状态机实现及示波器诊断,解决工业遥控、安防报警等场景中的解码难题,特别针对时序差异和干扰问题给出实用解决方案。

o0p1q2r3的博客 331

浪潮服务器ESXI6.7部署实战:手动集成RAID驱动避坑指南

本文详细介绍了在浪潮服务器上部署ESXI6.7时手动集成RAID驱动的完整流程和避坑指南。从驱动获取、解压技巧到封装实战,逐步解决国产服务器常见的驱动缺失问题,特别针对PM8060 RAID卡提供具体解决方案,帮助IT运维人员顺利完成ESXI部署。

weixin_33725722的博客 462

gcc/g++ 链接库的编译与链接

gcc/g++ 链接库的编译与链接 surgewong@gmail.com http://blog.youkuaiyun.com/surgewong       程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中,有些公共代码需要反复使用,就把这些代码编译成为“库”文件。在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中,这种库称为静态(链接)库,其特点是可

Surge 4万+

手把手教你用微信小程序实现智能硬件音视频通话(含Linux/RTOS适配)

本文详细介绍了如何利用微信小程序硬件框架(WMPF)和音视频SDK,在智能硬件(包括Linux与RTOS设备)上实现音视频通话功能。文章从架构设计、设备端SDK集成、小程序VOIP插件开发到跨平台调试与性能优化,提供了全链路实战指南,帮助开发者打通微信小程序与嵌入式设备间的实时音视频通信。

ooo22的博客 329

Win11系统下Abaqus 2025子程序开发环境一站式配置指南

本文详细介绍了在Win11系统下配置Abaqus 2025子程序开发环境的完整流程,包括VS2022和Intel oneAPI的安装与优化,以及Abaqus 2025的目录规划与环境联调。通过避坑指南和实战案例,帮助用户高效完成开发环境配置,避免常见错误,提升工作效率。

weixin_28717431的博客 334

Zabbix安装与部署 系统环境:centos7.0

Zabbix安装与部署 系统环境:centos7.0

weixin_42485027的博客 2600

Cursor自动生成测试用例

或者把你已有的规则直接复制粘贴到这个文档内(这里我用的是Markdown格式和CSV格式的测试用例生成规则,可以同时生成Markdown和CSV格式的测试用例)。在AI对话框这里,我们可以看到Cursor会逐步来帮我们生成测试用例,先分析识别需求文档,然后生成测试用例,最后会进行质量检查,确保生成的用例符合规范。生成的Markdown格式,可直接导入Xmind,生成思维导图,对应到具体的功能模块看具体的测试点,逻辑会更直观一些。(1)选中规则文档和需求文档:@你的测试用例规则文档@你的需求文档。

十二测试录 2433

ZYNQ BRAM实现PS与PL数据交互

有时CPU需要与PL进行小批量的数据交换,可以通过BRAM模块,也就是Block RAM实现此要求。本章通过Zynq的GP Master接口读写PL端的BRAM,实现与PL的交互。在本实验中加入了自定义的FPGA程序,并利用AXI4总线进行配置,通知其何时读写BRAM。以下为本实验原理图,CPU通过AXI BRAM Controller读取BRAM数据,CPU仅配置自定义的PL BRAM Controller 的寄存器,不通过它读写数据。

baidu_34971492的博客 2279

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

上一篇: 以太坊源码分析之 P2P网络(一、基础类或类型描述)
下一篇: 以太坊源码分析之 P2P网络(三、UDP底层通信)
还是要学会仰望
博客等级 码龄11年 19粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值