Liquid核心包有为PubSub定义接口,在core/broadcast/pubsub.go中broadcast.PubSub interface。
Liquid内置了一个该接口的实现——pubsub.ChainPubSub。ChainPubSub是基于应用协议实现的链级数据隔离消息发布及订阅服务。ChainPubSub代码在pubsub包中。
ChainPubSub将节点间连接关系划分为FullMsg、MetaDataOnly、FanOut三种状态。当某一节点发布消息时,FullMsg连接会传输全量数据;MetaDataOnly连接会传输元数据;FanOut连接会传输全量数据。元数据可以理解为仅包含消息的原始创建者和消息的编号,不包含详细信息。ChainPubSub使用类Gossip进行辅助传播等。
关于FanOut:假如有这样一个情况,节点A订阅了topicA,但是和A有连接的节点都没有订阅topicA,那A在该topic上发布一个消息,怎么传播到整个网络?答案就是FanOut。FanOut连接会传输全量数据,且允许连接中的一个节点没有订阅对应的topic。如果该节点由未订阅转换成已订阅,FanOut会立刻升级为FullMsg连接。同理处于FullMsg连接的节点如果取消订阅,则会降级为FanOut连接。这种连接状态在保持全量消息传输的同时,还起到了标识节点是否订阅的作用。
一个节点想要加入整个p2p网络,需要和种子节点创建连接,但是种子节点的数量有限,通常需要和其他非种子节点创建连接,才能达到足够的连接数量。如何让一个节点,发现其他节点的网络地址,就是节点发现服务需要实现的功能。
liquid的节点发现服务,其接口定义在core/discovery/discovery.go中discovery.Discovery interface。
Liquid内置了一个该接口的实现——protocoldiscovery.ProtocolBasedDiscovery。ProtocolBasedDiscovery是基于应用协议注册迭代查询实现的节点发现服务。ProtocolBasedDiscovery源代码在discovery/protocoldiscovery包中。
ChainMaker的P2P网络每一个节点即可以是客户端又可以是服务端,如果所有的节点都处于相同的网络环境中,一般不会出现问题。当不同的节点位于不同的内网环境中,节点与节点之间通信可能会遇到阻碍。例如下图所示:
假设node1和node2在内网1中,而node3和node4在内网2中,这时候node1和node2可以通过内网地址连接通信,node3和node4可以通过内网地址连接通信。但是内网1中的node1和node2,想要与内网2中的node3和node4建立P2P网络,就必须知道node3和node4的映射的外网地址,不然根本无法找到node3和node4与其通信。
Relay的功能
Liquid网络提供了中继转发的功能,当两个节点因为网络环境的原因,不能直接连接,可以利用中继节点,对流量数据进行转发,从而达到点对点传输的效果。如下图示:
Relay的基本流程
Relay的原理比较简单,假设中继节点为R,A节点可以拨号连接到中继节点R,B节点可以拨号连接到中继节点R,A节点通过中继节点R,将数据转发到B节点,这样使A和B节点也能做到数据交互。
下面引用libp2p中继流程描述:
phase I: Open a request for a relayed stream (A to R).
A dials a new stream sAR to R using protocol liquid-relay.
A sends a CircuitRelay message with { type: 'HOP', srcPeer: '/p2p/QmA', dstPeer: '/p2p/QmB' } to R through sAR.
R receives stream sAR and reads the message from it.
phase II: Open a stream to be relayed (R to B).
R opens a new stream sRB to B using protocol liquid-relay.
R sends a CircuitRelay message with { type: 'STOP', srcPeer: '/p2p/QmA', dstPeer: '/p2p/QmB' } on sRB.
R sends a CircuitRelay message with { type: 'STATUS', code: 'SUCCESS' } on sAR.
phase III: Streams are piped together, establishing a circuit
B receives stream sRB and reads the message from it
B sends a CircuitRelay message with { type: 'STATUS', code: 'SUCCESS' } on sRB.
B passes stream to NewConnHandler to be handled like any other new incoming connection.