目前进度为80%, 持续更新…
1 介绍
在开发IPFS(星际文件系统)的过程中,我们遇到了很多在异构设备之上运行分布式文件系统所带来的若干挑战,这些设备具有不同的网络设置和能力。在这个过程中,我们必须重新审视整个网络堆栈和详细的解决方案,以克服由多个网络层级和多种协议设计所施加的障碍,而不打破兼容性或再造技术。
为了构建这个库,我们专注于独立解决问题,创建具有复杂抽象的复杂的解决方案,当把关注点进行组合时,可以为P2P对等应用程序提供一个可靠的工作环境。
1.1 动机
libp2p 是我们建立分布式系统的集体经验的结果,因为它对开发者负责,决定他们希望应用程序如何与网络中的其他人进行交互,并支持配置和可扩展性,而不是对网络SETU做出假设。P.
本质上,使用LIPP2P的对等体应该能够使用各种不同的传输来与另一个对等体通信,包括连接中继,以及在不同的协议上进行协商,按需协商。
1.2 目标
libp2p 规范及其实现的目标是:
允许使用各种:
传输协议: TCP、UDP、SCTP、UDT、UTP、QIC、SSH等
认证传输协议:TLS,DTLS,CurveCP,SSH等
高效使用套接字(连接重用)
使对等体之间的通信在一个套接字上复用(避免握手开销)
使用协商过程使多种协议和不同协议版本在对等体之间使用
向后兼容
能在现有系统中工作
利用当前网络技术的全部能力
有 NAT 穿透功能
允许中继连接
启用加密通道
有效利用底层传输(例如本地流复用、本地AUTH等)。
2 网络协议栈的现状分析
本节向读者介绍了网络栈的可用协议和体系结构。我们的目标是提供推断结论的基础,并理解为什么 libp2p 提出了这些需求和体系结构.
2.1 客户端-服务器模型
客户端-服务器模型表明信道两端的双方具有不同的角色,它们支持不同的服务和/或具有不同的能力,或者换句话说,他们讲不同的协议。
构建客户机-服务器应用程序成为主流趋势有以下几个原因:
数据中心内部的带宽远远高于客户端可以互相连接的带宽。
数据中心资源相当便宜,由于有效的使用和散装备货。
开发人员和系统管理员更容易对应用程序进行细粒度的控制。
减少了要处理的异构系统的数量(虽然这个数字仍然相当可观)。
像NAT这样的系统使得客户机很难彼此查找并进行通信,迫使开发者执行非常聪明的hack来穿透这些障碍
Protocols started to be designed with the assumption that a developer will create a client-server application from the start.
我们甚至学会了如何使用Internet上的网关隐藏分布式系统的所有复杂性,使用协议来执行点对点操作(如HTTP),使得应用程序不透明地查看和理解每一次调用的服务调用级联.
libP2P提供了一种从客户端-服务器侦听器向拨号器侦听器交互的方法,其中不隐含哪些实体、拨号器或侦听器具有哪些能力或能够执行哪些操作。现在在两个应用程序之间建立连接是一个需要解决的多层问题,并且这些连接不应该有目的偏向,而应该支持多个其他协议来工作在已建立的连接之上。在客户机-服务器模型中,发送来自客户端的先前请求的服务器被称为推送模型,它通常会增加更多的复杂性;相比之下,在拨号侦听器模型中,两个实体可以独立地执行请求。
2.2 通过解决方案对网络栈协议进行分类
在深入到LIPP2P协议之前,重要的是了解已经广泛使用和部署的协议的多样性,这有助于维护今天的简单抽象。例如,当我们考虑HTTP连接时,人们可能天真地认为HTTP/TCP/IP是涉及的主要协议,但实际上更多的协议参与进来,这取决于使用情况、涉及的网络等。诸如DNS、DHCP、ARP、OSPF、以太网、802.11(Wi-Fi)等许多协议都涉及进来。看看ISPs内部网络,会发现更多的信息.
此外,值得注意的是,传统的7层OSI模型表征不适合于 libp2p. 作为替代的是,我们基于它们的角色来分类协议,即它们解决的问题。OSI模型的上层面向应用之间的点对点链路,而 libp2p 协议在各种不同的安全模型下更倾向于具有不同功能,不同大小的网络。不同的 libp2p 协议可以具有相同的角色(在OSI模型中,这将是“地址相同的层”),这意味着多个协议可以同时运行,所有处理一个角色(而不是传统的OSI堆叠中的每层协议)。例如,Bootstrap列表、mDNS、DHT发现 和 PEX 都是“对等发现”的形式,它们可以共存甚至协同。
2.2.1 构建物理链接
Ethernet
Wi-Fi
Bluetooth
USB
2.2.2 寻址机器或进程
IPv4
IPv6
Hidden addressing, like SDP
2.2.3 发现对等节点或服务
ARP
DHCP
DNS
Onion
2.2.4 通过网络路由消息
RIP(1, 2)
OSPF
BZGP
PPP
Tor
I2P
cjdns
2.2.5 传输
TCP
UDP
UDT
QUIC
WebRTC data channel
2.2.6 应用程序之间协商一致的通信语义
RMI
Remoting
RPC
HTTP
2.3 当前的缺陷
虽然我们目前有一系列的协议可供我们的服务进行通信,但解决方案的丰富性和多样性也产生了自身的问题。目前,应用程序难以通过多种传输机制(例如,在浏览器应用程序中缺少TCP/UDP栈)来支持和使用。
也没有“存在性链接”,这意味着没有一个对等体在多个传输中声明自己的概念,因此其他对等体可以保证它总是相同的对等体。
3 需求和注意事项
3.1 Transport agnostic(不确定的传输机制)
libp2p 是不依赖具体传输机制的,所以它可以运行在任何传输协议上。它甚至不依赖于IP,它可以运行在NDN、XI和其他新的互联网体系结构之上.
为了支持不同类型的传输机制,libp2p 使用 multiaddr,一种自描述寻址格式。这使得 libp2p 能够在系统中任意地处理地址,并且支持网络层中的各种传输协议。libp2p 中地址的实际格式是
ipfs-addr,以IPFS节点ID结束的 multi-addr。例如,这些都是有效的 ipfs-addrs:
# IPFS over TCP over IPv6 (typical TCP)
/ip6/fe80::8823:6dff:fee7:f172/tcp/4001/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu
# IPFS over uTP over UDP over IPv4 (UDP-shimmed transport)
/ip4/162.246.145.218/udp/4001/utp/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu
# IPFS over IPv6 (unreliable)
/ip6/fe80::8823:6dff:fee7:f172/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu
# IPFS over TCP over IPv4 over TCP over IPv4 (proxy)
/ip4/162.246.145.218/tcp/7650/ip4/192.168.0.1/tcp/4001/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu
# IPFS over Ethernet (no IP)
/ether/ac:fd:ec:0b:7c:fe/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu
注意:当前,尚不存在不可靠的实现。定义和使用不可靠传输的协议接口尚未定义。有关不可靠VS可靠传输的更多信息,请参见此处。在WebRTC的上下文中,在 这里 输入 CTRL+F搜索“可靠”。
3.2 多重多路复用(Multi-multiplexing)
libp2p 协议是多个协议的集合。为了节省资源,并使连接更容易,libp2p 可以通过一个端口,如TCP或UDP端口,根据所使用的传输来执行其所有操作。libp2p 可以通过点到点连接来复用它的许多协议。这种复用是用于可靠的流和不可靠的数据报。
libp2p 比较务实。它试图在尽可能多的配置中使用,以模块化和灵活的方式来适应各种用例,并尽可能少地选择。因此,libp2p 网络层提供了我们松散地称之为“多重多路复用”的内容:
- 多个网络接口的多路复用
- 多个传输协议的多路复用
- 多个对等连接的多路复用
- 可以复用多个客户端协议
- 每个协议/连接可以多路复用多个流(SPDY、HTTP2、QIC、SSH)
- 流量控制(背压,公平性)
- 用不同的临时密钥加密每个连接
举个例子,假设一个IPFS节点:
- 侦听特定的TCP/IP地址
- 侦听不同的TCP/IP地址
- 侦听特定的SCTP/UDP/IP地址
- 侦听特定的UDT/UDP/IP地址
- 具有与另一个节点X的多个连接
- 具有与另一个节点Y的多个连接
- 每个连接都有多个流打开
- 和 X 节点之间通过HTTP2协议复用流
- 和 Y 节点之间通过SSH复用流
- 挂载在 libp2p 顶部的协议为 每个对等节点使用一个流
- 挂载在 libp2p 顶部的协议为 每个对等节点使用多个流
如果不提供这种级别的灵活性将不可能在各种平台、场景或网络配置中使用 libp2p。
所有实现都支持所有的选择并不重要;关键的是,规范足够灵活,允许实现精确地使用他们所需要的。这确保了复杂的用户或应用程序约束不排除 libp2p 作为选项。
3.3 加密
在 libp2p 之上的通信可能是:
- 加密的
- 签名的(没有加密, 防篡改)
- clear(没有加密,也没有签名)
我们同时作了安全和性能考虑. 加密通信在数据中心内部高性能通信场景下不是很必要.
我们推荐:
- 默认加密所有的通信
- 实现需要被审计
- 除非绝对必要,用户通常只需要加密通信
Libp2p 采用类似TLS的加密套件.
Note: 我们不直接使用 TLS, 因为我们不需要 CA 系统包. 大多数 TLS 实现都非常庞大. Since libp2p model begins with keys, libp2p only needs to apply ciphers. 这只是整个 TLS 标准中很小的一部分.
3.4 NAT穿透
网络地址转换在英特网上无处不在. Not only are most consumer devices behind many layers of NAT, but most data center nodes are often behind NAT for security or virtualization reasons.
As we move into containerized deployments, this is getting worse. IPFS的实现 需要 提供一个方法来穿透 NAT, 否则操作可能会受到影响. 即使要用真实IP地址运行的节点也必须实现NAT穿越技术,因为它们可能需要建立与NAT后面的对等体的连接.
Libp2p 采用了类 ICE 的协议完成了完整的 NAT 穿透. 他并不完全是 ICE, 因为 IPFS 网络提供了通过IPFS协议中继通信的可能性, 用于协调穿孔甚至中继通信.
建议在实现中使用诸多可用的NAT穿透库之一,例如 libnice、libwebrtc 或 natty. 总而言之,NAT穿透必须是可互操作的.
3.5 中继(Relay)
然而,对于对称的NATS,容器和VM NAT,以及其他不可能绕过的NATS,libp2p 必须回退到中继通信,以建立一个完整的连通图。要完成这些,实现必须支持中继,虽然它应该是可选的,并且能够被终端用户关闭.
连接中继应该作为传输来实现,以便对上层透明.
中继的实现,可参考 p2p-circuit transport.
3.6 启用多种网络拓扑
不同的系统有不同的需求,进而导致不同的拓扑结构。在P2P文献中,我们可以发现这些拓扑被列举为:非结构化的、结构化的、混合的和集中式的.
集中式拓扑是在Web应用基础设施中最常见的拓扑结构,它要求给定的服务或服务群存在于已知的静态地址,以便其他服务能够访问它们。非结构化网络代表了一种P2P网络类型,其中网络拓扑结构是完全随机的,或者至少是非确定性的,而结构化网络具有隐组织的方式。混合网络是前两者的混合体.
考虑到这一点,libp2p 必须准备好执行不同的路由机制和对等点发现,以便构建将使服务能够传播消息或找到彼此的路由表.
3.7 资源发现(Resource discovery)
libp2p 还通过记录解决了网络内部资源的可发现性问题。记录是一个数据单元,可以被数字签名、时间戳和/或与其他方法一起使用,以使其具有短暂的有效性。这些记录保存诸如网络中存在的资源的位置或可用性等信息。这些资源可以是数据、存储、CPU周期和其他类型的服务。
libp2p 不应该限制资源的位置,而应该提供在网络中或旁路通道去方便查找资源的方式.
3.8 消息传输(Messaging)
高效的消息传递协议提供以最小等待时间传递内容的方法和/或支持用于分发的大型和复杂拓扑。LIPP2P试图结合多播和PUBSUB的发展来满足这些需求.
3.9 命名(Naming)
网络的变化和应用应该让使用者不感知具体的网络拓扑结构,命名便解决了这个问题.
4 架构
Libp2p 是遵循UNIX理念设计的, 它由很多易于创建和测试的小组件组成. 这些组件应该便于替换,以适应不同的技术或场景,并使其能够随着时间的推移而升级.
虽然不同的对等体可以根据它们的能力来支持不同的协议,但是任何对等体都可以充当拨号器和/或监听器,用于连接其他节点,一旦建立起的连接可以从两端重新使用,从而消除客户端和服务器之间的区别.
libp2p 接口在多个子系统上充当薄的粘合剂,以便对等体能够通信。只要它们尊重标准化的接口,这些子系统就可以建立在其他子系统的顶部。这些子系统适合的主要领域是:
点对点路由 – 机制来决定哪些节点用于路由特定消息。这种路由可以递归地、迭代地或甚至在广播/组播模式下完成.
Swarm – 处理所有从LBP2P中打开“流”部分的内容,从协议复用、流复用、NAT穿越和连接中继,同时支持多路传输.
存储和分发记录的系统。记录是其他系统用于信令、建立链接、宣布对等或内容等的小条目。它们在更广泛的互联网上与DNS有相似的作用.
发现-发现或识别网络中的其他对等体.
这些子系统中的每一个都公开了一个众所周知的接口(见第6章),并且可以相互使用以实现其目标。系统的全局概览:
libp2p
Peer Routing
Swarm
Distributed Record Store
Discovery
4.1 Peer Routing
对等路由子系统公开一个接口,以标识消息在DHT中应该路由到哪些对等点。它接收一个密钥并且必须返回一个或多个 PeerInfo 对象.
我们提出了两个可能的对等路由子系统的例子,第一个基于 Kademlia DHT,第二个基于 mDNS. 然而,只要实现相同的期望和接口,就可以实现更多的对等路由机制.
kad-routing
mDNS-routing
Other-routing-mechanisms
Peer Routing
4.1.1 kad-routing
kad-routing 路由实现了 Kademlia 路由表,其中每个节点持有一组k桶,每个桶包含来自网络中其他对等点的多个 PeerInfo 对象.
4.1.2 mDNS-routing
mDNS路由 使用 mDNS 探针来识别局域网节点是否具有给定的密钥,或者它们只是简单地存在.
4.2 Swarm
4.2.1 Stream Muxer
流复用器必须实现接口流复用器提供的接口.
4.2.2 Protocol Muxer
协议复用是在应用层级别处理的,而不是在端口级别(不同的服务/协议在不同端口监听)的常规方式. 这使得我们能够过支持多个协议在同一个套接字中进行加密,从而节省了为多个端口进行NAT穿透的成本.
协议复用是通过多流协议来完成的, 该协议使用 multicodec 来协商不同类型的流(协议).
4.2.3 Transport
4.2.4 Crypto
4.2.5 Identify
Identify is one of the protocols mounted on top of Swarm, our Connection handler. However, it follows and respects the same pattern as any other protocol when it comes to mounting it on top of Swarm. Identify enables us to trade listenAddrs and observedAddrs between peers, which is crucial for the working of IPFS. Since every socket open implements REUSEPORT, an observedAddr by another peer can enable a third peer to connect to us, since the port will be already open and redirect to us on a NAT.
4.2.6 回放(Replay)
See Circuit Relay.
4.3 分布式记录存储(Distributed Record Store)
4.3.1 Record
Follows IPRS spec.
4.3.2 abstract-record-store 4.3.3 kad-record-store
4.3.4 mDNS-record-store
4.3.5 s3-record-store
4.4 Discovery
4.4.1 mDNS-discovery
mDNS-发现 是一种在局域网上使用 mDNS 的发现协议。它发射了mDNS信标来查找是否有更多的对等体可用。局域网节点对于对等协议是非常有用的,因为它们的低延迟链路.
mDNS-发现是一种独立的协议,不依赖于任何其他的 libp2p 协议。在不依赖其他基础设施的情况下,mDNS-发现 可以产生局域网中可用的对等点. 这在内联网、与互联网主干断开的网络以及暂时失去链路的网络中尤其有用.
mDNS-discovery 可以针对每个服务进行配置(i.e. 即仅发现参与特定协议的对等体,如IPFS), 还有私有网络(发现属于专用网络的对等体).
我们正在探索加密 mDNS-discovery beacons 的方式 (使得本地网络中的其他节点无法识别正在使用的服务), though the nature of mDNS will always reveal local IP addresses.
隐私注意:mDNS 在局域网中进行广告,在同一本地网络中向听众显示IP地址。不推荐使用隐私敏感的应用程序或太公开的路由协议.
4.4.2 random-walk
随机游走是DHTS(具有路由表的其他协议)的发现协议。它进行随机DHT查询,以便快速了解大量的对等体。这导致DHT(或其他协议)收敛得更快,而在初始阶段需要承担一定负载开销.
4.4.3 bootstrap-list
Bootstrap列表是一种发现协议,它使用本地存储来缓存网络中可用的高度稳定的(和一些可信的)对等点的地址。这允许协议“找到网络的其余部分”。这基本上与DNS自举的方式相同(尽管注意到,通过设计改变DNS引导列表——“点域”地址——不容易做到).
该列表应该存储在长期本地存储中,无论这意味着本地节点(例如磁盘)。
协议可以将默认列表硬编码或采用标准代码分发机制(如DNS)进行传送。
在大多数情况下(当然在IPFS的情况下),引导列表应该是用户可配置的,因为用户可能希望建立单独的网络,(or place their reliance and trust in specific nodes)或者将它们的信任和信任放在特定的节点中.
4.5 Messaging
4.5.1 PubSub
参考 https://github.com/libp2p/spe…
4.5.1.1 实现
PubSub 开发正在进行中, 用 floodsub 作为初始协议, 随后是 gossipsub, 这是2018年5月发布的alpha版本.
Floodsub 的实现包括:
go-libp2p-floodsub: 参考 此篇 作为上下文. 还有 这篇 关于 gossipsub;
rust-libp2p-floodsub: @jamesray1 正基于 gossipsub 之上研究和开发. Js-libp2p-floodsub.
4.6. Naming
4.5.2 IPNS
5 Data structures
网络协议主要处理以下数据结构:
PrivateKey: 节点的私钥
PublicKey: 节点的公钥
PeerId: 节点公钥的hash
PeerInfo: 包含节点的 PeerId, 及其已知的 multiaddr 对象.
Transport: 用于建立与其他对等体的连接. 参考 https://github.com/libp2p/int…
Connection: 节点之间的点对点连接. 必须实现 https://github.com/libp2p/int…
Muxed-Stream: 双工通信信道.
Stream-Muxer: 流复用器. 必须实现 https://github.com/libp2p/int…
Record: IPLD(IPFS链接数据) 描述了实现 IPRS 的对象.
multiaddr: 自描述的网络地址. 参考 https://github.com/multiforma…
multicodec: 自描述的编码类型. 参考 https://github.com/multiforma…
multihash: 自描述的hash. 参考 https://github.com/multiforma…
- 接口
=====
libp2p 是多个协议的集合,它们一起工作,提供一个可以与任何其他网络可寻址进程对话的公共实体接口。这是通过将现有的协议和实现摆在一组显式接口中来实现的:对等路由、发现、Stream Muxing、传输、连接等。
6.1 libp2p
libp2p, 作为顶级模块,为其它接口提供 libp2p 实例, must offer an interface for dialing to a peer and plugging in all of the modules (e.g. which transports) we want to support. We present the libp2p interface and UX in section 6.6, after presenting every other module interface.
6.2 Peer Routing
A Peer Routing 模块为 libp2p 提供节点查找其它节点 PeerInfo 信息的方式, 以便连接节点. 最简单的形式, Peer Routing 模块需要一个接口,其接收一个 ‘key’, 并返回一个 PeerInfo 的集合. 关于接口和测试可参考 https://github.com/libp2p/int…
6.3 Swarm
Current interface available and updated at:
https://github.com/libp2p/js-…
6.3.1 Transport
https://github.com/libp2p/int…
6.3.2 Connection
https://github.com/libp2p/int…
6.3.3 Stream Muxing
https://github.com/libp2p/int…
6.4 Distributed Record Store
https://github.com/libp2p/int…
6.5 Peer Discovery
A Peer Discovery module interface should return PeerInfo objects, as it finds new peers to be considered by our Peer Routing modules.
6.6 libp2p interface and UX
libp2p 实现应该使它能够以编程方式实例化,或者使用以前编译过的库,并且已经进行了一些协议决策,以便用户可以重用或扩展.
程序化构建一个LIPP2P实例
用JavaScript实现的例子, 可以被映射到其他语言:
var Libp2p = require(‘libp2p’)
var node = new Libp2p()
// add swarm instance
node.addSwarm(swarmInstance)
// add one or more Peer Routing mechanisms
node.addPeerRouting(peerRoutingInstance)
// add a Distributed Record Store
node.addDistributedRecordStore(distriburedRecordStoreInstance)
配置 libp2p 非常简单,因为大多数配置来自于实例化多个模块,一个接一个.
拨号和侦听与对等体的连接
理想情况下,libp2p 使用自己的机制(Peer Routing and Record Store)找到拨号给给定对等点的方法.
node.dial(PeerInfo)
若要接收传入连接,请指定要处理的一个或多个协议:
node.handleProtocol('<multicodec>', function (duplexStream) {
})
查找对等节点
找到对等点是通过对等路由,所以接口是相同的.
存储和检索记录
像查找对等体一样,通过记录存储来完成记录的存储和检索,所以接口是相同的。
7 Properties
7.1 通信模型 – 流(Communication Model – Streams)
7.2 Ports – Constrained Entrypoints
7.3 传输协议
7.4 无IP网络
Efforts like NDN and XIA are new architectures for the internet, which are closer to the model IPFS uses than what IP provides today. IPFS will be able to operate on top of these architectures trivially, as these are no assumptions made about the network stack in the protocol. Implementations will likely need to change, but changing implementations is vastly easier than changing protocols.
8 实现
在实现不同的模块和功能时,libp2p2 的实现应该(推荐)遵循一定级别的粒度,以便公共接口易于暴露、测试和检查与其他实现的互操作性.
这是当前 libp2p 存在的模块列表:
libp2p(entry point)
Swarm
- libp2p-swarm
- libp2p-identify
- libp2p-ping
Transports
- Interface-transport
- Interface-connection
- libp2p-tcp
- libp2p-udp
- libp2p-udt
- libp2p-utp
- libp2p-webrtc
- libp2p-cjdns
Stream Muxing
- Interface-stream-muxer
- libp2p-spdy
- libp2p-multiplex
Crypto Channel
- libp2p-tls
- libp2p-secio
Peer Routing
- libp2p-kad-routing
- libp2p-mDNS-routing
- Discovery
- libp2p-mdns-discovery
- libp2p-random-walk
- libp2p-railing
Distributed Record Store
- libp2p-record
- interface-record-store
- libp2p-distributed-record-store
- libp2p-kad-record-store
Generic
- PeerInfo
- PeerId
- multihash
- multiaddr
- multistream
- multicodec
- ipld
- repo
当前已知实现(WIP):
- JavaScript – https://github.com/libp2p/js-…
- Go – https://github.com/ipfs/go-li…
- Python – https://github.com/candeira/p…
- Rust – https://github.com/libp2p/rus…
8.1 集群(Swarm)
8.1.1 Swarm Dialer
集群拨号器管理到目标对等体的成功连接,给定一个地址流作为输入,并且确保遵守速率限制等约定.
为此,我们设计了以下的拨号逻辑:
DialPeer(peerID) {
if PeerIsBeingDialed(peerID) {
waitForDialToComplete(peerID)
return BestConnToPeer(peerID)
}
StartDial(peerID)
waitForDialToComplete(peerID)
return BestConnToPeer(peerID)
}
StartDial(peerID) {
addrs = getAddressStream(peerID)
addrs.onNewAddr(function(addr) {
if rateLimitCanDial(peerID, addr) {
doDialAsync(peerID, addr)
} else {
rateLimitScheduleDial(peerID, addr)
}
})
}
// doDialAsync starts dialing to a specific address without blocking.
// when the dial returns, it releases rate limit tokens, and if it
// succeeded, will finalize the dial process.
doDialAsync(peerID, addr) {
go transportDial(addr, function(conn, err) {
rateLimitReleaseTokens(peerID, addr)
if err != null {
// handle error
}
dialSuccess(conn)
})
}
// rateLimitReleaseTokens checks for any tokens the given dial
// took, and then for each of them, checks if any other dial is waiting
// for any of those tokens. If waiting dials are found, those dials are started
// immediately. Otherwise, the tokens are released to their pools.
rateLimitReleaseTokens(peerID, addr) {
tokens = tokensForDial(peerID, addr)
for token in tokens {
dial = dialWaitingForToken(token)
if dial != null {
doDialAsync(dial.peer, dial.addr)
} else {
token.release()
}
}
}