今天小编给各位分享的是泛洪协议,深入理解以太坊 P2P 网络设计的知识,,希望对您有所帮助。如果你还想了解更多这方面的信息,请点击本站其他相关内容,共同学习吧!如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文导读目录:
泛洪协议,深入理解以太坊 P2P 网络设计 ♂
深入理解以太坊 网络设计
前言
在设计公链时,节点与节点之间建立连接需要 协议,从而实现数据的同步,于此同时上层应用还需要封装一些通信逻辑,比如节点之间的区块同步、交易数据同步等。
本篇文章将对 网络发展进行简单概述,同时将从源码角度对以太坊中的节点发现机制、分布式哈希表、节点查找、节点新增、节点移除等进行简单介绍,并对其 网络安全性设计进行简要分析。
基础知识网络
网络不同于传统的 结构,在 网络中每个节点既可以是客户端也可以是服务端,节点之间的通信协议一般直接通过 实现。
技术发展至今经历了以下四个发展阶段:
一.集中式:
是 网络模式中最简单的路由方式,即存在一个中心节点,它保存了其他所有节点的索引信息,索引信息一般包括节点 、端口、节点资源等,集中式路由的优点是结构简单、实现容易,但缺点也很明显,由于中心节点需要存储所有节点的路由信息,当节点规模不断扩展时,就很容易出现性能瓶颈,而且也存在单节点故障问题
二.分布式:
是指移除了中心节点,在 节点之间建立随机网络,在新加入节点与 网络中的某个节点之间随机建立连接通道,从而形成一个随机拓扑结构,新节点加入该网络时随机选择一个已经存在的节点并建立邻居关系,在节点与邻居节点建立连接后,还需要进行全网广播,让整个网络知道该节点的存在
具体的广播步骤是:该节点首先向邻居节点广播,邻居节点收到广播消息后,在继续向自己的邻居节点广播,以此类推,这种广播方式也被称之为"泛洪机制",而泛洪机制的问题在于可控性差,其主要包括两个较大的问题:一个是容易形成泛洪循环,比如节点 给发出的消息结果节点 到节点 ,节点 再广播到节点 ,这就形成了一个循环,另一个问题是消息响应分包,比如节点 想要请求的资源被很多节点所拥有,那么在短时间内,会出现大量节点同时向 节点发送响应消息,这就很可能让节点瞬间奔溃
而消除泛洪循环的方法可以借鉴 网络路由协议中有关泛洪广播的控制,一种方法是对每一个查询消息设置值,泛洪消息每被转发一次, 值减 ,当节点接受的 为 时,不再转发消息,这样可以避免查询消息在网络中产生死循环,还可以为泛洪消息设置唯一的标志,对接收到的重复消息不再进行转发从而规避死循环,解决响应风暴的方法可以在数据链路层进行网络分段,减少消息跨段广播
三.混合式:
混合式其实就是混合集中式和分布式结构,网络中存在很多超级节点组成的分布式网络,而每个超级节点有多个普通节点与它组成局部集中网络,一个新的普通节点加入是可以先选择一个超级节点进行通信,该超级节点再推送其他超级节点列表给新加入节点,加入节点根据列表中的超级节点状态决定选择那个具体的超级节点作为父节点,这种结构的泛洪广播只是发生在超级节点之间,因此可以避免大规模泛洪问题,在实际应用中,混合式结构是相对灵活且比较有效的组网架构,实现难度也相对较小,因此目前较多系统基于混合式结构进行开发实现
四.结构化:
结构化 网络是一种分布式网络结构,与上面所讲的分布式结构不同,分布式网络就是一个随机网络,而结构化网络则将所有节点按照某种结构进行有序组织,比如形成一个环状网络或树状网络,结构化网络在具体实现上普遍基于分布式哈希表 ( ,) 算法,具体的实现方案有 、、、 等算法
四种网络结构对比如下:
节点发现
节点发现是任何节点接入 网络的第一步,节点发现可以分为两种:
-初始节点发现:
指节点是一个全新的、从未运行的节点,该节点没有网络中的其他节点的任何数据,此时节点发现只能依靠节点中的硬编码的种子节点获得 网络的信息
-已知节点发现:
节点之前运行过,节点数据库中保存着网络中的其他节点信息,此时节点发现可以依靠节点数据库汇总的节点获取 网络的信息,从而构建自己的网络拓扑
种子节点
在 网络中,初始节点在启动时会通过一些长期稳定运行的节点快速发现网络中的其他节点,这些节点被称为"种子节点"(一般代码中会硬编码种子节点信息),一般情况下种子节点可以分为两种:
-:
也被称之为"种子节点", 是互联网提供的一种域名查询服务,它将域名和 地址相互映射保存在一个分布式的数据库中,当我们访问 服务器时,给它提供一个域名, 服务器会将该域名对应的 地址返回
-:
即将种子节点的 地址硬编码到代码中去,硬编码的这些节点的地址被称为种子节点
算法
是一种分布式哈希表()技术,与其他 技术相比, 算法使用异或算法计算节点之间的距离,进而建立了全新的 拓扑结构,这种算法可以极大地提高路由的查询速度。
哈希表是用于存储键值对的一种容器,键值对有被称为 / 对,哈希表数据结构中包含 个 (桶),对于某个具体的哈希表, (桶的数量)通常是固定不变的,于是可以对每个桶编号,~-,桶是用来存储键值对的,可以简单的将其理解为一个动态数组,里面存放多个键值对。
下图展示了哈希表的查找原理,我们可以方便快速地通过 来获取 ,当使用某个 进行查找时,先用某个哈希函数计算这个 的哈希值,得到的哈希值通常是一个整数,之后使用哈希值对(桶数)进行取模运算(除法求余数),就可以算出对应的桶编号
说到哈希表不得不提一下哈希表碰撞,当两个不同的 进行哈希计算得到相同的哈希值时,就是所谓的哈希函数碰撞,一旦出现这种情况,这两个 对应的两个键值对就会被存在在同一个桶中 () 中,另一中散列碰撞是虽然计算出来的哈希值不同,但经过取模运算之后得到相同的桶编号,这时候也会将两个键值对存储在一个桶中,哈希碰撞原理如下图所示:
如果某个哈希表在存储数据时完全没有碰撞,那么每个桶里都只有 个或 个键值对,这样查找起来就非常快,反之,如果某个哈希表在存储数据时出现严重碰撞,那么就会导致某些桶里存储了很多键值对,那么在查找 的时候需要在这个桶里面逐一对比 是否相同,查找效率会变得很低~
分布式哈希表
分布式哈希表在概念上类似于传统的哈希表,差异在于传统的哈希表主要用于单机上的某个软件中,分布式哈希表主要用于分布式系统(此时,分布式系统的节点可以通俗的理解为 表中的 ),分布式哈希表主要用于存储大量(甚至海量)的数据,分布式哈希表的原理如下图所示:
源码分析以太坊底层的 大致可以分为以下三层:
-顶层:
以太坊中各个协议的具体实现
-中层:
以太坊中的 通信链路层,负责启动监听、处理新加入连接或维护连接
-底层:
以太坊中的数据通信网络 层,主要负责路由表的管理以及数据库的读写操作
表的结构
表数据结构如下所示:
// :--..\\\.
(
= //
= //
= // -
// /
// ' ' ' .
= (.{}) *
= / //
= - //
// .
, = , // /
, = ,
= * .
= * .
= * .
= * .
=
= * * .
)
{
. // , , ,
[]* //
[]* //
*. // ,
.
.
*. //
{}
{}
{}
{}
(*) //
}
{
[]* // ,
[]* //
.
}
关键的几个变量:
:桶,每个桶包含节点(依据最近活跃情况进行降序排列),用于按距离列出已知节点索引:种子节点,一个节点启动的时候最多能够链接个种子节点,其中有五个是以太坊官方指定的,另外个从数据库里面提取:用于存储节点的数据库(以太坊中有两个,另一个用于存储链上数据):刷新-桶事件的管道表的创建
函数用于创建新的表:
// :--..\\\.
( , *., []*., .) (*, ) {
:= &{
: ,
: ,
: ( {}),
: ( {}),
: ( {}),
: ( {}),
: .(.()),
: .{: , : },
: ,
}
:= .(); != {
,
}
:= . {
.[] = &{
: .{: , : },
}
}
.()
.()
,
}
在上述代码中首先使用传入的参数初始化了一个的对象,调用函数设置初始链接节点(即获得个节点,后面如果为空或者数据库中没有节点信息时这些节点将被用于去链接网络),之后通过一个循环结合函数来验证节点是否有效
之后初始化桶:
:= . {
.[] = &{
: .{: , : },
}
}
之后从.中随机取个节点加载种子节点到相应的:
.()
.()
,
函数的具体实现如下所示(这里的为.中最上方定义的全局变量,值为):
// : --..\\\.
( *) () {
:= (..(, ))
= (, ....)
:= {
:= []
:= .{: () {} { .(..(.(), .())) }}
..(" ", "", .(), "", .(), "", )
.()
}
}
这里的即用于添加节点到,在这里会检查要添加的节点是否已经存在以及是否已满,如果已满则调用.(, )将节点添加到列表中去,之后添加,之后更新:
// : --..\\\.
( *) ( *) {
.() == .().() {
}
..()
..()
:= .(.())
(., .()) {
// , ' .
}
(.) >= {
// , .
.(, )
}
!.(, .()) {
// ' : .
}
// :
. = (., )
. = (., )
. = .()
. != {
.()
}
}
事件监听
函数是.中的主循环,在函数开头出定义了后续会使用到的局部变量,之后通过进行刷新桶操作,在这里的循环会每隔分钟自动刷新一次桶,每隔秒钟验证桶节点是否可以通,每秒将桶中存在超过分钟的节点存储本地数据库,视作稳定节点:
// : --..\\\.
= * .
= * .
= * .
// : --..\\\.
// , .
( *) () {
(
= .(.())
= .()
= .()
= ( {}) //
{} //
= [] {}{.} //
)
.()
.()
.()
// .
.()
:
{
{
<-.: //定时刷新桶事件,=
.()
== {
= ( {})
.()
}
:= <-.: //刷新桶的请求事件
= (, )
== {
= ( {})
.()
}
<-: // 完成
_, := {
()
}
, = ,
<-.: // 验证桶节点有效性,
= ( {})
.()
<-: // 验证桶节点有效性完成
.(.())
=
<-.: // 定时(秒)将节点存入数据库,如果某个节点在桶中存在超过分钟,则认为它是一个稳定的节点
.()
<-.:
}
}
!= {
<-
}
_, := {
()
}
!= {
<-
}
(.)
}
节点查找
函数用于根据来查找节点,如果不存在则返回:
// ' .
( *) ( .) *. {
..()
..()
:= .()
_, := . {
.() == {
()
}
}
}
节点发现
以太坊分布式网络采用了结构化网络模型,其实现方案使用协议,下面我们对节点发现进行简单介绍,在以太坊中值是,也就是说每个桶包含个节点,一共个桶,桶中记录节点的,,,等信息,并按照与节点的距离排序,节点查找由()实现:
// :--..\\\.
( *) ( {}) {
()
.()
// .
..()
:= ; < ; ++ {
..()
}
}
从上述代码中可以看到这里首先调用.()从数据库中加载节点并将其插入到表中去:
// : --..\\\.
( *) () {
:= (..(, ))
= (, ....)
:= {
:= []
:= .{: () {} { .(..(.(), .())) }}
..(" ", "", .(), "", .(), "", )
.()
}
}
// :--..\\\.
//
// .
( *) ( , .) []* {
(
= .()
= ([]*, , )
= ..(, )
)
.()
:
:= ; () < && < n*5; seeks++ {
ctr := id[0]
rand.Read(id[:])
id[0] = ctr + id[0]%16
it.Seek(nodeKey(id))
n := nextNode(it)
if n == nil {
id[0] = 0
continue seek // iterator exhausted
}
if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > {
}
:= {
[].() == .() {
//
}
}
= (, )
}
}
之后通过来发现新的节点,这里会优先使用当前节点的来运行发现邻居节点:
// : --..\\\_.
// .
// .
( *) () []*. {
.(., .().()).()
}
// : --..\\\_.
( *) ( ., .) * {
(, ., , ( *) ([]*, ) {
.(, )
})
}
( ., *, ., ) * {
:= &{
: ,
: ,
: ([.]),
: ([.]),
: {: },
: ( []*, ),
: .(),
: -,
}
// ' .
// .
.[.().()] =
}
最后随机一个,进行:
// : --..\\\_.
( *) () []*. {
.(.).()
}
// : --..\\\_.
( *) ( .) * {
.
.([:])
.(, )
}
( *) ( ., .) * {
(, ., , ( *) ([]*, ) {
.(, )
})
}
// .
( *) ( *, .) ([]*, ) {
(
= (, .())
= {: }
)
[]*.
, = .((), )
== {
,
}
_, := {
.() != .().() {
.((), )
}
}
.,
}
// :--..\\\_.
// .
( *) ( *., []) ([]*., ) {
:= .(, ., &.{: })
.(, )
}
( *) ( *., , .) * {
:= &{
: ,
: ,
: ,
: ([], ),
: ( ., ),
: ( , ),
}
// .
.(.)
.(.)
// .
{
. <- :
<-..():
. <-
}
}
服务结构
端的的数据结构如下所示:
// :--..\\.
// .
{
// .
// .
// .
(., *.)
(*)
(, ) (., )
. //
.
*
. // ,
.
.
*.
*.
*.
*.
*.
*
// .
{}
*.
*.
{}
*
*
// .
}
配置(本地节点秘钥、拨号比率、节点最大链接数、拨号比率、事件记录等):
// .
{
*. `:"-"`
`:","`
`:","`
`:","`
`:"-"`
[]*.
[]*. `:","`
[]*.
[]*.
*. `:","`
`:","`
[] `:"-"`
. `:","`
`:"-"`
`:","`
. `:","`
.
}
#####
新增节点
函数用于新增一个给定的节点,其实现代码如下所示:
// :--..\\.
( *) ( *.) {
..()
}
// :--..\\.
// .
( *) ( *.) {
{
. <- :
<-..():
}
}
函数用于新增一个可信任节点:
//
// , .
( *) ( *.) {
{
. <- :
<-.:
}
}
移除节点
函数用于移除节点并断开与节点之间的连接:
// :--..\\.
( *) ( *.) {
(
*
.
)
// .
.(( [.]*) {
..()
:= [.()]; != {
= ( *, )
= ..()
.()
}
})
// .
!= {
.()
:= {
. == .() && . == {
}
}
}
}
// :--..\\.
// .
( *) ( *.) {
{
. <- :
<-..():
}
}
函数用于移除一个可信任节点:
// .
( *) ( *.) {
{
. <- :
<-.:
}
}
终止服务
函数用于终止节点运行,具体代码如下所示:
( *) () {
..()
!. {
..()
}
. =
. != {
//
..()
}
(.)
..()
..()
}
服务启动
位于--..\\.中的函数用于启动一个节点:
// :--..\\.
( *) () ( ) {
..()
..()
. {
.(" ")
}
. =
. = ..
. == {
. = .()
}
. == {
. = .{}
}
. && . == "" {
..(" , ")
}
//
. == {
.(". - ")
}
. == {
. =
}
. == {
. = .
}
. = ( {})
. = ( )
. = ( *)
. = ( *)
. = ( *.)
. = ( *.)
. = ( )
. = ( {})
:= .(); != {
}
. != "" {
:= .(); != {
}
}
:= .(); != {
}
.()
..()
.()
}
在这里首先检查当前节点是否处于运行状态,如果是则直接返回并给出错误提示信息,如果不是则将.设置为,之后进入服务启动流程,之后检查是否开启等,之后初始化配置服务信息:
// .
// - .
( *) () ( ) {
..()
..()
. {
.(" ")
}
. =
. = ..
. == {
. = .()
}
. == {
. = .{}
}
. && . == "" {
..(" , ")
}
//
. == {
.(". - ")
}
. == {
. =
}
. == {
. = .
}
. = ( {})
. = ( )
. = ( *)
. = ( *)
. = ( *.)
. = ( *.)
. = ( )
. = ( {})
之后调用来启动一个本地节点,并建立本地监听,然后配置一个网络协议,生成节点路由表。
之后调用启动主动拨号连接过程,然后开一个协程,在其中做的维护:
.()
..()
.()
}
代码如下所示,这里通过来建立连接,参数确定了进行主动建立连接时的节点集,它是一个迭代器 ,同时将连接建立函数传入:
( *) () {
:= {
: ..(),
: .(),
: .,
: .,
: .,
: .,
: .,
}
. != {
. = .
}
. == {
. = {&.{: }}
}
. = (, ., .)
_, := . {
..()
}
}
函数如下所示,在这里通过.()从迭代器中取得节点,之后通过通道传入.()中进行连接:
// :--..\\.
( , ., ) * {
:= &{
: .(),
: ,
: ([.]*),
: ([.]*),
: ([.]),
: ( *),
: ( *.),
: ( *.),
: ( *.),
: ( *),
: ( *),
}
. = ..()
., . = .(.())
..()
.()
.()
}
服务监听
在上面的服务启动过程中有一个函数,该函数用于监听事件,具体代码如下所示:
( *) () {
// .
, := .("", .)
!= {
}
. =
. = .().()
// .
, := .().(*.); {
..(.(.))
!..() && . != {
..()
() {
.(., ., "", ., ., " ")
..()
}()
}
}
..()
.()
}
在上述代码中又调用了一个.(),该函数是一个死循环的,它会监听端口并接收外部的请求:
//
// .
( *) () {
..(" ", "", ..())
// .
:=
. > {
= .
}
:= ( {}, )
:= ; < ; ++ {
<- {}{}
}
// .
// .
..()
() {
:= ; < (); ++ {
<-
}
}()
{
// .
<-slots
var (
fd net.Conn
err error
lastLog time.Time
)
for {
fd, err = srv.listener.Accept()
if netutil.IsTemporaryError(err) {
if time.Since(lastLog) > *. {
..(" ", "", )
= .()
}
.(. * )
} != {
..(" ", "", )
<- {}{}
}
}
:= .(.())
:= .(); != {
..(" ", "", .(), "", )
.()
<- {}{}
}
!= {
*.
, := .().(*.); {
=
}
= (, , )
..(" ", "", .())
}
() {
.(, , )
<- {}{}
}()
}
}
这里的主要执行执行握手协议,并尝试把链接创建为一个对象:
//
// .
// .
( *) ( ., , *.) {
:= &{: , : , : ( )}
== {
. = .(, )
} {
. = .(, .())
}
:= .(, , )
!= {
.()
}
}
在上述代码中又去调用了.(, , )函数,该函数用于执行握手协议:
( *) ( *, , *.) {
// .
..()
:= .
..()
! {
}
// , .
*.
!= { // = 被动连接,!=主动连接诶
= (.)
:= .((*.)()); != {
= .(" ' ")
..(" ", "", ..(), "", ., "", )
}
}
// .
, := .(.) // 公钥交换,确定共享秘钥层面的握手一来一去
!= {
..(" ", "", ..(), "", ., "", )
}
!= {
. =
} {
. = (, .)
}
:= ..("", ..(), "", ..(), "", .)
= .(, .)
!= {
.(" ", "", )
}
// .
, := .(.) // 进行协议层面的握手,也即握手,一来一去
!= {
.(" ", "", )
}
:= ..(); !.(.(.), [:]) {
.(" ", "", .(.))
}
., . = ., .
= .(, .) // 状态校验
!= {
.(" ", "", )
}
}
秘钥握手通过函数实现,在函数之中调用了()函数:
// :--..\\.
( *) ( *.) (*., ) {
..(.().())
..()
}
代码如下所示,在这里会根据是主动握手还是被动握手来进行执行对应的握手逻辑:
// :--..\\\.
( *) ( *.) (*., ) {
(
)
. != { //主动握手
, = (., , .) //主动发起秘钥验证握手结束,确定共享秘钥
} { // 被动握手
, = (., )
}
!= {
,
}
.()
.,
}
主动发起握手过程过程如下,在这里会调用来生成身份信息,包含签名,随机生成的与签名对应的公钥和版本号,之后调用方法进行编码,之后发起加密握手,之后接收返回的消息,并验证解密,获取对方公钥,之后生成,:
// :--..\\\.
( ., *., *.) ( , ) {
:= &{: , : .()}
, := .()
!= {
,
}
, := (, )
!= {
,
}
_, = .(); != {
,
}
:= ()
, := (, , , )
!= {
,
}
:= .(); != {
,
}
.(, )
}
如下所示,和相差无几:
( ., *.) ( , ) {
:= ()
, := (, , , )
!= {
,
}
:= ()
:= .(, ); != {
,
}
, := .()
!= {
,
}
[]
. {
, = .()
} {
, = (, )
}
!= {
,
}
_, = .(); != {
,
}
.(, )
}
之后通过来完成协议握手操作,在这里调用发送一次握手操作,之后通过来读取返回信息,之后进行检查:
( *) ( *) ( *, ) {
:= ( , )
() { <- (, , ) }()
, = (); != {
<- //
,
}
:= <-werr; err != nil {
return nil, fmt.Errorf("write error: %v", err)
}
// If the protocol version supports Snappy encoding, upgrade immediately
t.conn.SetSnappy(their.Version >= )
,
}
服务循环
函数是服务的主循环,监听服务器终止、增加信任节点、移除信任节点、增加检查节点等:
( *) () {
..(" ", "", ..().())
..()
..()
..()
..()
(
= ([.]*)
=
= ([.], (.))
)
_, := . {
[.()] =
}
:
{
{
<-.:
:= <-.:
..(" ", "", )
[.()] =
, := [.()]; {
..(, )
}
:= <-.:
..(" ", "", )
(, .())
, := [.()]; {
..(, )
}
:= <-.:
()
. <- {}{}
:= <-.:
[..()] {
. |=
}
. <- .(, , )
:= <-.:
:= .(, , )
== {
:= .()
[..()] =
..(" ", "", (), "", .(), "", ., "", .(), "", .())
..()
.() {
++
}
}
. <-
:= <-srv.delpeer:
// A peer disconnected.
d := common.PrettyDuration(mclock.Now() - pd.created)
delete(peers, pd.ID())
srv.log.Debug("Removing p2p peer", "peercount", len(peers), "id", pd.ID(), "duration", d, "req", pd.requested, "err", pd.err)
srv.dialsched.peerRemoved(pd.rw)
if pd.Inbound() {
inboundCount--
}
}
}
srv.log.Trace("P2P networking is spinning down")
if srv.ntab != nil {
srv.ntab.Close()
}
if srv.DiscV5 != nil {
srv.DiscV5.Close()
}
for _, p := range peers {
p.Disconnect(DiscQuitting)
}
for len(peers) > {
:= <-.
..("<- ()")
(, .())
}
}
节点信息
用于查看节点信息,用于查看连接的节点信息:
// .
( *) () * {
:= .()
:= &{
: .,
: .(),
: .().(),
: .().(),
: .,
: ([]{}),
}
.. = .()
.. = .()
. = .()
_, := . {
_, := .[.]; ! {
:= {}("")
:= .; != {
= .()
}
.[.] =
}
}
}
( *) () []* {
// -
:= ([]*, , .())
_, := .() {
!= {
= (, .())
}
}
//
:= ; < (); ++ {
:= + ; < len(infos); j++ {
if infos[i].ID > []. {
[], [] = [], []
}
}
}
}
请求处理
下面为.函数的代码:
( *) () ( , ) {
(
= ( {}, )
= ( , )
= ( , )
//
)
..()
.()
.()
// .
<- {}{}
.(, )
// .
:
{
{
= <-:
// .
// .
!= {
=
}
<- {}{}
= <-:
, := .(); {
=
=
} {
=
}
= <-.:
= ()
= <-.:
= ()
}
}
(.)
..()
..()
,
}
从上述代码中可以看到函数的开头首先定义了一些局部变量,之后启用了两个协程,一个是,它通过调用()读取,之后又通过调用.()来处理
如果是,则发送一个回应,如果与下述特殊情况不相匹配则将交给通道,等待.()从通道中取出。另一个协程是,它主要通过调用(., )来发起请求
之后调用()函数让协议运行起来:
( *) ( <- {}, <- ) {
..((.))
_, := . {
:=
. = .
. =
. =
=
. != {
= (, ., .(), ., .().., .()..)
}
..(.(" %/%", ., .))
() {
..()
:= .(, )
== {
..(.(" %/% ", ., .))
=
} != . {
..(.(" %/% ", ., .), "", )
}
. <-
}()
}
}
最后通过一个循环来处理错误或者断开连接等操作:
// .
:
{
{
= <-:
// .
// .
!= {
=
}
<- {}{}
= <-:
, := .(); {
=
=
} {
=
}
= <-.:
= ()
= <-.:
= ()
}
}
(.)
..()
..()
,
创数据库
函数用于创建一个持久化的数据库用于存储节点信息:
// :--..\\\.
// / ,
// .
( ) (*, ) {
:= &.{: }
, := .(, )
_, := .(*.); {
, = .(, )
}
!= {
,
}
:= ([], .)
= [:.(, ())]
, := .([](), )
{
.:
// (.. ),
:= .([](), , ); != {
.()
,
}
:
// ,
!.(, ) {
.()
= .(); != {
,
}
()
}
}
&{: , : ( {})},
}
节点超时
函数用于检查节点是否超时,具体实现代码如下所示:
( *) () {
..(() { .() })
}
( *) () {
:= .()
.()
{
{
<-.:
.()
<-db.quit:
return
}
}
}
func (db *DB) expireNodes() {
it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil)
defer it.Release()
if !it.Next() {
return
}
var (
threshold = time.Now().Add(-dbNodeExpiration).Unix()
youngestPong int64
atEnd = false
)
for !atEnd {
id, ip, field := splitNodeItemKey(it.Key())
if field == dbNodePong {
time, _ := binary.Varint(it.Value())
if time > {
=
}
< threshold {
// Last pong from this IP older than threshold, remove fields belonging to it.
deleteRange(db.lvl, nodeItemKey(id, ip, ""))
}
}
atEnd = !it.Next()
nextID, _ := splitNodeKey(it.Key())
if atEnd || nextID != id {
if youngestPong > && < {
(., ())
}
=
}
}
}
状态更新
下面是一些状态更新函数:
//
// .
( *) ( , .) . {
= .(); == {
.{}
}
.(.((, , )), )
}
// .
( *) ( , ., .) {
= .(); == {
}
.((, , ), .())
}
// .
( *) ( , .) . {
= .(); == {
.{}
}
//
.()
.(.((, , )), )
}
// .
( *) ( , ., .) {
= .(); == {
}
.((, , ), .())
}
// .
( *) ( , .) {
= .(); == {
}
(.((, , )))
}
// .
( *) ( , ., ) {
= .(); == {
}
.((, , ), ())
}
// .
( *) ( , .) {
= .(); == {
}
(.((, , )))
}
// .
( *) ( , ., ) {
= .(); == {
}
.((, , ), ())
}
节点挑选
函数用于从数据库里面随机挑选合适种子节点:
//
// .
( *) ( , .) []* {
(
= .()
= ([]*, , )
= ..(, )
)
.()
:
:= ; () < && < n*5; seeks++ {
ctr := id[0]
rand.Read(id[:])
id[0] = ctr + id[0]%16
it.Seek(nodeKey(id))
n := nextNode(it)
if n == nil {
id[0] = 0
continue seek // iterator exhausted
}
if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > {
}
:= {
[].() == .() {
//
}
}
= (, )
}
}
( .) * {
:= ; !; = !.() {
, := (.())
() != {
}
([:], .())
}
}
总结
网络是区块链分布式网络结构的基础,本篇文章详细介绍了网络的基本原理,包括节点发现机制、分布式哈希表、节点查找、节点新增、节点移除、请求处理等,同时从源码角度对以太坊源码中网络的实现做了较为细致的分析,探索了以太坊网络的工作流程以以及安全设计,而公链安全体系的建设依旧是长路漫漫,有待进一步深入探索
泛洪协议,深入理解以太坊 P2P 网络设计的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于泛洪协议,深入理解以太坊 P2P 网络设计的信息别忘了在本站进行查找喔。
还没有评论,来说两句吧...