节点发现服务(findings)
前言
提供各种类型节点的登记注册,维护一个节点连系信息的暂存池,向请求目标类型节点信息的用户提供连系清单。同时,作为P2P网络的基础设施,为新节点的加入提供 NAT打洞
服务支持,因为findings已经是一个网络,所以可以多机协作而无需双IP配置。
一台服务器维护的节点信息包含同类节点(findings
)、数据驿站的 depots/archives
和 depots/blockqs
公共服务节点,以及不同区块链的工作节点。
通常,应用节点会启动一个自己的findings服务器,该服务器通过多种方式搜寻其它findings服务节点进行组网。如果存在已知的公共findings节点,组网会很快,否则可能是一个比较耗时的过程。如果需要服务器实时可用,运行一个自己的长期在线的findings是一个不错的选择。
除了对 depots/archives, blockqs
公共服务节点会普遍支持外,对于不同的区块链应用,findings服务器会有自己的选择。服务器通常会先声明自己支持的区块链的名称,并向请求节点信息的区块链应用传递一个相同链的地址,用于接收可能有的奖励。
用法
(略)
性能
(略)
附:网络的连通性
暴力发现
传统的P2P网络有一个致命的缺陷:起始中枢。即新节点刚上线时需要获得其它节点的信息以建立初次连接,这时就需要一个提供这些信息的中心服务器。而一旦这样的服务器被禁用,P2P的逻辑就执行不起来。
目前解决这个问题的办法有几个,比如在App发布时就加入几个主要节点的IP,或者通过专用的渠道获取。但实际上这些都可以被封堵。
findings是一个网络,通过大众化的利益驱动模式运行且成本低廉。如果网络已经启动运行,假以时日,数以万计的服务器规模是可预期的。如果无需顾及时间,用IP遍历的 暴力发现 方式去发现这个已经运行的网络,则不应该是一件太困难的事。findings服务器可以仅仅只是一台旧手机或树莓派,可以长期挂机,因此这个P2P网络就会很大。
用户用一台小设备挂机扫描,暴力发现findings网络,一旦成功,用户就无需担心自己的具体类P2P网络找不到了。
App发布时会默认配置几个IP,这些IP至少曾经有效,用户也可以修改这个配置。作为一种暴力发现优化,这些IP会作为随机IP的衍生起点,新创建的IP会围绕着它。这是基于友好用户可能成群结队的假设。同时,这也可用于IP簇优化:配置一个簇中间的一个IP号,然后随机衍生的IP就会在它附近。
标准端口混入
如果管理者屏蔽了Findings服务的默认端口号,我们可以采用通用服务端口混入的方式,比如 https 的标准端口 443。因为大多数的公网IP都不会提供这种标准服务,所以在这个端口提供 findings 服务也是可行的。
这需要公网IP主机们的共同努力,来应对这种网络封锁。
动态端口(含工作量)
网络阻断可能针对特定的端口,目的是阻断某种特定的应用通讯。对抗这种封锁的一个方法就是采用动态端口的策略。
这里的动态端口是可预测的,预测算法加入了工作量的逻辑,用户需要一定量的计算才能获知目标端口。这种策略迫使攻击者也需要付出同样的代价,如果每一台服务器的动态端口都不一样,那么阻断这种通讯就需要大量的工作量运算。而对于单一节点而言,它们只连接少量的服务器,计算消耗是可以接受的。
算法是通用的,但会加入服务器自身的IP地址作为动态因素。目标端口将在一定的时间段(如1小时)内保持不变,来获得确定性。
算法示意
随机动态端口的算法加入了工作量机制。代码仅为示意,但借助了Go语言的语法高亮友好。
// App配置项:
// 服务器和客户端其值相同,在App发布时设置。
var timeFrom time.Time = GetConfig("timeFrom") // 起始计算时间。
var randBase int64 = GetConfig("randBase") // 随机数种子。
var diffValue [32]byte = GetConfig("diffValue") // 难度标的值(前置零越多则越难)。
// 参与运算的随机因子
// 这些值在当前App运行期间动态获取。
var IP netip.AddrPort // 服务器网址(IP+Port)
var hours int64 // 起始时间到当前时间的小时数
var portRnd int64 // 端口随机源
// 随机值确定性
rand.Seed( randBase )
// 随机值边界
const randMax = 1<<63 - 1
// 循环尝试:
// 难度标的值(diffValue)通常会设置在普通单机需运算约1~2秒的程度。
// 工作量在此产生。
for {
// 以小时为单位
// 即:1小时内该值不会变,获得一种确定性。
hours = time.Since(timeFrom) % time.Hour
// 端口随机源
portRnd = rand.Intn(randMax)
// 随机因子串联,计算哈希
// 注:仅为示意,实际代码需要转换后串联。
tmp := sha256.Sum256( IP + hours + portRnd )
// 是否满足目标难度
if tmp < diffValue {
break
}
}
// 目标端口,
// +1024,使不占用系统端口,端口上界 65535
return (portRnd % 64511) + 1024
为避免计算出来的端口与服务器本机上其它端口冲突,可以约定2个备用端口:即目标端口 +1
和 +2
。如计算的端口是 1234
,则 1235
(1234+1)和 1236
(1234+2)都为备用端口。