美文网首页以太坊原理解析
[以太坊源码分析][p2p网络04]:基于UDP的节点发现

[以太坊源码分析][p2p网络04]:基于UDP的节点发现

作者: jea的笔记本 | 来源:发表于2019-02-03 20:59 被阅读0次

    为什么要进行节点发现呢?

    因为要加入一个p2p网络,并且与网络中的节点交互,需要知道这个p2p网络中的一些节点信息。节点发现,使本地节点得知其他节点的信息,进而加入到p2p网络中。节点发现就是一个寻找邻居节点的过程。

    这里有一点跟去中心化违背的地方,就是节点第一次启动的时候,节点会与硬编码在以太坊源码中的bootnode进行连接,这个bootnode有一种中心化服务器的感觉,因为所有的节点加入几乎都先连接了它。然而,只有一个可以通信的节点,明显是不足够的。连接上bootnode后,获取bootnode部分的邻居节点,然后进行节点发现,获取更多的活跃的邻居节点。

    以太坊的节点发现

    Kademlia - wiki
    kademlia
    以太坊的节点发现基于类似的kademlia算法,源码中有两个版本,v4和v5。v4适用于全节点,通过discover.ListenUDP使用,v5适用于轻节点通过discv5.ListenUDP使用,这里主要介绍v4版本,较为简单的版本。

    接下来是以太坊节点发现v4的源码分析部分,分为udp发现k桶刷新

    0.索引

    01.p2p服务开启节点发现
    02.udp节点发现
    03.k桶刷新

    1.p2p服务开启节点发现

    p2p.server.go中的Start方法中:

    if !srv.NoDiscovery {
        cfg := discover.Config{
            PrivateKey:   srv.PrivateKey,
            AnnounceAddr: realaddr,
            NodeDBPath:   srv.NodeDatabase,
            NetRestrict:  srv.NetRestrict,
            Bootnodes:    srv.BootstrapNodes,
            Unhandled:    unhandled,
        }
        ntab, err := discover.ListenUDP(conn, cfg)
        if err != nil {
            return err
        }
        srv.ntab = ntab
    }
    
    • 其中discover.ListenUDP方法即开启了节点发现的功能。discover.ListenUDP方法创建了一个新的udp对象(在discover/udp.go中),用于节点发现,和Table对象(在discover/table.go中),用于维护k桶。
      func ListenUDP(c conn, cfg Config) (*Table, error) {
        tab, _, err := newUDP(c, cfg)
        ...
        return tab, nil
      }
      

    2.udp节点发现

    新建一个udp对象

    首先要从newUDP方法看起,newUDP方法如下:

    func newUDP(c conn, cfg Config) (*Table, *udp, error)
    
    udp

    源码中三个主要的过程:

    • 1.newTable方法新建Table对象,并且开启了k桶刷新(即更新路由表)的功能。这部分在下面的内容再做介绍。
    • 2.go udp.loop()协程,循环的监听4个通道。
      • case <-t.closing,检测是否停止。
      • case p := <-t.addpending,检测是否有添加新的待处理消息。
      • case r := <-t.gotreply,检测是否接收到其他节点的回复消息。
      • case now := <-timeout.C,检测是否延时。
    • 3.go udp.readLoop(cfg.Unhandled)协程。
      • 循环接收其他节点发来的udp消息。
        nbytes, from, err := t.conn.ReadFromUDP(buf)
      • 处理接收到的udp消息。
        t.handlePacket(from, buf[:nbytes])

    udp消息有4种:

    • ping,用于判断远程节点是否在线。
    • pong,用于回复ping消息的响应。
    • findnode,查找与给定的目标节点相近的节点。
    • neighbors,用于回复findnode的响应,与给定的目标节点相近的节点列表。
    节点发现的过程

    (假设节点A要进行节点发现,向节点B进行查询)

    • 1.节点A向节点B发送ping消息,判断节点B是否在线。节点A加入与该ping消息对应的pending。(这里使用了t.addpending通道。)
      对应的方法:
      func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error
      
    • 2.节点A收到节点B发来的pong消息,确认节点B在线。将pongpending进行匹配。(这里使用了t.gotreply通道。)
      对应的方法:
      func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error
      
    • 3.节点A向节点B发送findnode消息,想要获取与目标节点相近的节点。发送findnode消息时,会先检测上次收到节点B的pong消息是否超过24小时,超过则发送ping消息,接收到pong消息后再发送findnode消息。同时也记录一个与findnode消息对应的pending。(这里使用了t.addpending通道。)
      对应的方法:
      func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error)
      
    • 4.节点A收到节点B发来的neighbors消息,获取到几个与目标节点相近的节点。将neighborspending进行匹配。(这里使用了t.gotreply通道。)
      对应的方法:
      func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error
      
    • 5.节点A向新获取的节点继续进行上述4个步骤,直到查找完成。

    其中,节点B启动了loopreadloop两个单独的协程来处理节点A发送来的消息。

    3.k桶刷新

    以太坊的k桶设置:
    alpha为3
    nBuckets,k桶数量为17
    bucketSize,k桶中最多存16个节点
    maxReplacements,每个k桶的候选节点列表最多存10个节点

    新建一个Table对象

    newTable方法如下:

    func newTable(t transport, self *enode.Node, db *enode.DB, bootnodes []*enode.Node)(*Table, error)
    
    Table

    主要介绍三个方法:

    • 1.tab.setFallbackNodes(bootnodes)方法,设置初始引导节点,验证其完整性,然后加入引导节点列表。
      初始引导节点的作用,如开头所说,首次启动并且没有加入到此p2p网络的节点,要加入到网络中,必须知道网络中一些节点的信息。初始引导节点的作用便是如此:引导初始启动的节点加入到p2p网络中。
    • 2.tab.loadSeedNodes()方法,从保留已知节点的数据库中随机的抽取30个节点,再加上引导节点列表中的节点,放置入k桶中。
    • 3.go tab.loop()协程,刷新k桶。下面介绍。
    k桶刷新的过程

    也就是go tab.loop()协程具体做了什么,如下:

    loop
    主要介绍三个协程:
    • 1.go tab.doRefresh(refreshDone),刷新的协程。
      doRefresh
      主要的查找逻辑在lookup里,lookup会对3个异或距离较近的节点进行查询,查询方法用到的是udp.findnode。将每一次的查询结果,根据距离范围,加入到k桶中,如果k桶未满。
    • 2.go tab.doRevalidate(revalidateDone),重新验证的协程。选取随机的k桶中的最后一个节点,使用udpping消息,如果ping通了,将该节点移动在k通中的最前面。如果ping不通,删除该节点,从replacements候选节点列表中选取节点加入k桶。
    • 3.go tab.copyLiveNodes(),节点入数据库的协程。将k桶中的节点存入数据库中,选取节点的条件是节点在k桶中存在5分钟以上。

    4.总结

    • 1.以太坊的节点发现分为两个部分,基于udp的节点发现k桶的刷新维护机制
    • 2.基于udp的节点发现使用ping消息去确定远程节点是否在线,以及通过findnode消息去查找到更多的远程节点。其中使用了两个独立的协程,loopreadloop,用于对接收到远程节点的udp消息进行处理。
    • 3.k桶的刷新维护机制,也就是时时更新节点的路由表,每30分钟进行一次查找新的节点,每10秒进行一次k桶中节点的重新验证,每30秒进行一次k桶节点的入库操作。其中使用了三个独立的协程。

    相关文章

      网友评论

        本文标题:[以太坊源码分析][p2p网络04]:基于UDP的节点发现

        本文链接:https://www.haomeiwen.com/subject/jeissqtx.html