BOLT 7: P2P 节点和通道发现
本文档描述了简单的节点发现、通道发现和通道更新机制,这些机制不依赖于第三方来传播信息。 本文详细介绍了short_channel_id的定义、announcement_signatures消息、channel_announcement消息、node_announcement消息、channel_update消息、查询消息、重新广播、HTLC费用、修剪网络视图以及路由建议。
BOLT #7: P2P 节点和通道发现
本规范描述了简单的节点发现、通道发现和通道更新机制,这些机制不依赖于第三方来传播信息。
节点和通道发现服务于两个不同的目的:
- 节点发现允许节点广播其 ID、主机和端口,以便其他节点可以打开连接并与它们建立支付通道。
- 通道发现允许创建和维护网络拓扑的本地视图,以便节点可以发现到达所需目的地的路由。
为了支持通道和节点发现,支持三种 gossip 消息:
-
对于节点发现,节点交换
node_announcement消息,该消息提供有关节点的附加信息。可能存在多个node_announcement消息,以便更新节点信息。 -
对于通道发现,网络中的节点交换
channel_announcement消息,其中包含有关两个节点之间新 通道的信息。他们还可以交换channel_update消息,以更新有关通道的信息。对于任何通道,只能有一个有效的channel_announcement,但预计至少有两个channel_update消息。
目录
- short_channel_id 的定义
announcement_signatures消息channel_announcement消息node_announcement消息channel_update消息- 查询消息
- 重播
- HTLC 费用
- 修剪网络视图
- 路由建议
- 参考
short_channel_id 的定义
short_channel_id 是资金交易的唯一描述。
它的构造如下:
- 最高有效 3 字节:指示区块高度
- 接下来 3 字节:指示区块内的交易索引
- 最低有效 2 字节:指示支付给通道的输出索引。
short_channel_id 的标准人类可读格式是
通过打印上述组件来创建的,顺序为:
区块高度、交易索引和输出索引。
每个组件都打印为十进制数,
并用小写字母 x 相互分隔。
例如,short_channel_id 可以写成 539268x845x1,
表示高度为 539268 的区块中索引为 845 的交易的输出 1 上的通道。
理由
short_channel_id 人类可读格式的设计
使得双击或双击它将在大多数系统上选择整个 ID。
人们在阅读数字时更喜欢十进制,
因此 ID 组件以十进制书写。
使用小写字母 x,因为在大多数字体上,
x 明显小于十进制数字,
从而可以轻松地以可见方式对 ID 的每个组件进行分组。
announcement_signatures 消息
这是通道两个端点之间的直接消息,用作选择加入机制,以允许将通道通告给网络的其余部分。
它包含发送者构造 channel_announcement 消息所需的签名。
- 类型:259 (
announcement_signatures) - 数据:
- [
channel_id:channel_id] - [
short_channel_id:short_channel_id] - [
signature:node_signature] - [
signature:bitcoin_signature]
- [
发起节点宣布通道的意愿在通道打开期间通过设置 channel_flags 中的 announce_channel 位来发出信号(参见 BOLT #2)。
要求
announcement_signatures 消息是通过构造与新确认的通道资金交易相对应的 channel_announcement 消息,
并使用与端点的 node_id 和 bitcoin_key 匹配的密钥对其进行签名来创建的。
一个节点:
- 如果
open_channel消息设置了announce_channel位,并且尚未发送shutdown消息:- 在已发送和接收
channel_ready之后,并且资金交易具有足够的确认以确保它不会被重组:- 必须为资金交易发送
announcement_signatures。
- 必须为资金交易发送
- 在已发送和接收
- 否则:
- 不得发送
announcement_signatures消息。
- 不得发送
- 重新连接时(一旦满足上述时间要求):
- 如果它之前没有收到资金交易的
announcement_signatures:- 必须发送自己的
announcement_signatures消息。
- 必须发送自己的
- 如果它收到资金交易的
announcement_signatures:- 必须使用自己的
announcement_signatures消息进行响应。
- 必须使用自己的
- 如果它之前没有收到资金交易的
接收节点:
- 如果
short_channel_id不正确:- 应该发送
warning并关闭连接,或者发送error并使通道失败。
- 应该发送
- 如果
node_signature或bitcoin_signature不正确:- 可以发送
warning并关闭连接,或者发送error并使通道失败。
- 可以发送
- 如果它已发送并且收到了有效的
announcement_signatures消息:- 如果资金交易至少有 6 个确认:
- 应该将其对等方的
channel_announcement消息排队。
- 应该将其对等方的
- 如果资金交易至少有 6 个确认:
- 如果它尚未发送
channel_ready:- 应该推迟处理
announcement_signatures,直到发送channel_ready之后。
- 应该推迟处理
理由
在资金交易有足够的
确认之前,不得宣布通道,因为区块链重组会使
short_channel_id 无效。
channel_announcement 消息
此 gossip 消息包含有关通道的所有权信息。它将
每个链上比特币密钥与关联的闪电网络节点密钥相关联,反之亦然。
在至少一方使用 channel_update 宣布其费用水平和到期时间之前,该通道实际上是不可用的。
证明 node_1 和 node_2 之间存在通道需要:
- 证明资金交易支付给
bitcoin_key_1和bitcoin_key_2 - 证明
node_1拥有bitcoin_key_1 - 证明
node_2拥有bitcoin_key_2
假设所有节点都知道未花费的交易输出,则第一个证明是
由节点找到 short_channel_id 给出的输出并
验证它是否确实是 BOLT #3 中指定的那些密钥的 P2WSH 资金交易输出来完成的。
最后两个证明是通过显式签名完成的:
为每个 bitcoin_key 生成 bitcoin_signature_1 和 bitcoin_signature_2,
并对每个相应的 node_id 进行签名。
还需要证明 node_1 和 node_2 都同意
公告消息:这是通过让每个
node_id(node_signature_1 和 node_signature_2)对消息进行签名来完成的。
- 类型:256 (
channel_announcement) - 数据:
- [
signature:node_signature_1] - [
signature:node_signature_2] - [
signature:bitcoin_signature_1] - [
signature:bitcoin_signature_2] - [
u16:len] - [
len*byte:features] - [
chain_hash:chain_hash] - [
short_channel_id:short_channel_id] - [
point:node_id_1] - [
point:node_id_2] - [
point:bitcoin_key_1] - [
point:bitcoin_key_2]
- [
要求
原始节点:
- 必须将
chain_hash设置为唯一标识通道在其中打开的链的 32 字节哈希:- 对于 比特币区块链:
- 必须将
chain_hash值(以十六进制编码)设置为等于6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000。
- 必须将
- 对于 比特币区块链:
- 必须将
short_channel_id设置为引用已确认的资金交易, 如 BOLT #2 中所述。- 注意:相应的输出必须是 P2WSH,如 BOLT #3 中所述。
- 必须将
node_id_1和node_id_2设置为运营通道的两个节点的公钥, 使得node_id_1是按升序词典顺序排序的两个压缩密钥中按字典顺序较小的那个。 - 必须将
bitcoin_key_1和bitcoin_key_2设置为node_id_1和node_id_2各自的funding_pubkey。 - 必须计算消息的双 SHA256 哈希
h,从偏移量 256 开始,直到消息结束。- 注意:哈希跳过 4 个签名,但哈希消息的其余部分, 包括附加到末尾的任何未来字段。
- 必须将
node_signature_1和node_signature_2设置为哈希h的有效签名 (使用node_id_1和node_id_2各自的密钥)。 - 必须将
bitcoin_signature_1和bitcoin_signature_2设置为哈希h的有效签名 (使用bitcoin_key_1和bitcoin_key_2各自的密钥)。 - 必须根据 BOLT #9 设置为此通道协商的功能的
features - 必须将
len设置为保存其设置的features位所需的最小长度。 - 如果资金交易的确认次数少于 6 次:
- 不得发送
channel_announcement。
- 不得发送
接收节点:
- 必须通过验证签名来验证消息的完整性和真实性。
- 如果
features字段中存在未知的偶数位:- 不得尝试通过该通道路由消息。
- 如果
short_channel_id的输出与 P2WSH 不对应(使用bitcoin_key_1和bitcoin_key_2,如 BOLT #3 中所述)或输出已花费:- 必须忽略该消息。
- 如果指定的
chain_hash是接收者未知的:- 必须忽略该消息。
- 如果
short_channel_id的输出没有至少 6 个确认:- 如果输出接近 6 个确认,则可以接受该消息,以防 接收节点尚未收到最新的区块。
- 否则:
- 应该忽略该消息。
- 否则:
- 如果
bitcoin_signature_1、bitcoin_signature_2、node_signature_1或node_signature_2无效或不正确:- 应该发送
warning。 - 可以关闭连接。
- 必须忽略该消息。
- 应该发送
- 否则:
- 如果
node_id_1或node_id_2已被列入黑名单:- 应该忽略该消息。
- 否则:
- 如果所引用的交易之前未作为通道宣布:
- 应该将消息排队以进行重播。
- 可以选择不处理长于预期最小长度的消息。
- 如果所引用的交易之前未作为通道宣布:
- 如果它之前收到了有效的
channel_announcement,对于 同一交易,在同一区块中,但对于不同的node_id_1或node_id_2:- 应该将先前消息的
node_id_1和node_id_2列入黑名单, 以及此node_id_1和node_id_2,并忘记与它们连接的任何通道。
- 应该将先前消息的
- 否则:
- 应该存储此
channel_announcement。
- 应该存储此
- 如果
- 如果
- 一旦其资金输出已被花费或重新组织出来:
- 应该在延迟 12 个区块后忘记一个通道。
理由
两个节点都需要签名以表明他们愿意通过此通道路由其他 付款(即成为公共网络的一部分);要求他们的 比特币签名证明他们控制该通道。
冲突节点的黑名单不允许多个不同的 公告。任何 节点都不应广播此类冲突的公告,因为这暗示着密钥已泄漏。
虽然不应在通道足够深入之前进行宣传,但 针对重播的要求仅适用于交易尚未转移到 不同区块的情况。
为了避免存储过大的消息,同时仍然允许合理的未来扩展, 允许节点限制重播(可能在统计上)。
将来可能会有新的通道功能:向后兼容(或 可选)的功能将具有 奇数 功能位,而不兼容的功能 将具有 偶数 功能位 (“成为奇怪的人是可以的!”)。
当忘记资金输出花费上的通道时,会使用 12 个区块的延迟,
以允许新的 channel_announcement 传播,该公告指示此
通道已被拼接。
node_announcement 消息
此 gossip 消息允许节点指示与其关联的额外数据, 除了其公钥之外。为避免琐碎的拒绝服务攻击, 忽略与已知通道不关联的节点。
- 类型:257 (
node_announcement) - 数据:
- [
signature:signature] - [
u16:flen] - [
flen*byte:features] - [
u32:timestamp] - [
point:node_id] - [
3*byte:rgb_color] - [
32*byte:alias] - [
u16:addrlen] - [
addrlen*byte:addresses]
- [
timestamp 允许在多个
公告的情况下对消息进行排序。rgb_color 和 alias 允许情报服务将
节点分配颜色,如黑色,并将 酷炫 的绰号分配给“IRATEMONK”和“WISTFULTOLL”。
addresses 允许节点宣布其接受传入网络
连接的意愿:它包含一系列用于连接到
节点的 address descriptor。第一个字节描述地址类型,后跟
该类型的适当数量的字节。
定义了以下 address descriptor 类型:
1:ipv4;数据 =[4:ipv4_addr][2:port](长度 6)2:ipv6;数据 =[16:ipv6_addr][2:port](长度 18)3:已弃用(长度 12)。过去用于包含 Tor v2 onion 服务。4:Tor v3 onion 服务;数据 =[35:onion_addr][2:port](长度 37)- 版本 3 (prop224)
onion 服务地址;编码:
[32:32_byte_ed25519_pubkey] || [2:checksum] || [1:version],其中checksum = sha3(".onion checksum" || pubkey || version)[:2]。
- 版本 3 (prop224)
onion 服务地址;编码:
5:DNS 主机名;数据 =[1:hostname_len][hostname_len:hostname][2:port](长度最多 258)hostname字节必须是 ASCII 字符。- 非 ASCII 字符必须使用 Punycode 编码: https://en.wikipedia.org/wiki/Punycode
要求
原始节点:
- 必须将
timestamp设置为大于先前已创建的任何node_announcement的时间戳。- 可以将其基于 UNIX 时间戳。
- 必须将
signature设置为signature之后剩余的整个数据包的双 SHA256 的签名(使用node_id给出的密钥)。 - 可以设置
alias和rgb_color以自定义其在地图和 图形中的外观。- 注意:
rgb_color的第一个字节是红色值,第二个字节是 绿色值,最后一个字节是蓝色值。
- 注意:
- 必须将
alias设置为有效的 UTF-8 字符串,其中任何alias尾随字节 等于 0。 - 应该使用期望传入连接的每个公共网络
地址的地址描述符填充
addresses。 - 必须将
addrlen设置为addresses中的字节数。 - 必须按升序顺序放置地址描述符。
- 不应在任何位置放置任何零类型地址描述符。
- 应该仅将放置用于对齐
addresses之后的字段。 - 不得创建
port等于 0 的type 1、type 2或type 5地址描述符。 - 应确保
ipv4_addr和ipv6_addr是可路由的地址。 - 必须根据 BOLT #9 设置
features - 应该将
flen设置为保存其设置的features位所需的最小长度。 - 不应宣布 Tor v2 onion 服务。
- 不得宣布多个
type 5DNS 主机名。
接收节点:
- 如果
node_id不是有效的压缩公钥:- 应该发送
warning。 - 可以关闭连接。
- 不得进一步处理该消息。
- 应该发送
- 如果
signature不是有效签名(使用node_id的双 SHA256 的整个消息在signature字段之后,包括 附加到末尾的任何未来字段):- 应该发送
warning。 - 可以关闭连接。
- 不得进一步处理该消息。
- 应该发送
- 如果
features字段包含 未知的偶数位:- 不应连接到该节点。
- 除非支付没有设置相同位(s) 的 BOLT #11 发票,否则不得尝试向该节点_发送_付款。
- 不得_通过_该节点路由付款。
- 应该忽略与以上定义的类型不匹配的第一个
address descriptor。 - 如果
addrlen不足以保存已知类型的地址描述符:- 应该发送
warning。 - 可以关闭连接。
- 应该发送
- 如果
port等于 0:- 应该忽略
ipv6_addr或ipv4_addr或hostname。
- 应该忽略
- 如果
node_id之前不是从channel_announcement消息中已知的, 或者如果timestamp不大于从此node_id收到的最后一个node_announcement:- 应该忽略该消息。
- 否则:
- 如果
timestamp大于从此node_id收到的最后一个node_announcement:- 应该将消息排队以进行重播。
- 可以选择不将长于预期最小长度的消息排队。
- 如果
- 可以使用
rgb_color和alias来引用界面中的节点。- 应该暗示其自签名来源。
- 应该忽略 Tor v2 onion 服务。
- 如果宣布了多个
type 5地址:- 应该忽略其他数据。
- 不得转发
node_announcement。
理由
将来可能会有新的节点功能:向后兼容(或
可选)的功能将具有 奇数 feature 位,而不兼容的功能将具有
偶数 feature 位。这些将正常传播;此处不兼容的
功能位是指节点,而不是 node_announcement 消息
本身。
将来可能会添加新的地址类型;由于地址描述符必须
按升序排列,因此可以安全地忽略未知的地址描述符。
将来也可能会添加 addresses 之外的其他字段——如果它们需要特定的对齐方式,则可以在 addresses 中进行可选填充。
节点别名的安全注意事项
节点别名是用户定义的,并提供了一种潜在的注入攻击途径,无论是在渲染过程中还是在持久化过程中。
节点别名应始终在 HTML/Javascript 上下文或任何其他动态解释的渲染 框架中显示之前进行清理。同样,请考虑使用准备好的语句、输入验证和转义来防止支持 SQL 或其他动态解释的查询语言的注入漏洞和持久化引擎。
不要像 Little Bobby Tables 的学校一样。
channel_update 消息
在最初宣布通道后,每一方都会独立
宣布它需要通过此通道中继 HTLC 的费用和最小到期时间增量。
每一方都使用与
channel_announcement 匹配的 8 字节通道 shortid 和 1 位 channel_flags 字段来指示它位于通道的哪一端(源或最终)。
节点可以多次执行此操作,
以更改费用。
请注意,channel_update gossip 消息仅在
中继 付款的上下文中才有用,而不是 发送 付款。在进行付款
A -> B -> C -> D 时,只有与通道相关的 channel_update 与通道
B -> C(由 B 宣布)和 C -> D(由 C 宣布)才会
发挥作用。在构建路由时,需要从目的地到源向后计算 HTLC 的金额和到期时间。用于
路由中最后一个 HTLC 的 amount_msat 的确切初始值和 cltv_expiry 的最小值在付款请求中提供
(请参见 BOLT #11)。
- 类型:258 (
channel_update) - 数据:
- [
signature:signature] - [
chain_hash:chain_hash] - [
short_channel_id:short_channel_id] - [
u32:timestamp] - [
byte:message_flags] - [
byte:channel_flags] - [
u16:cltv_expiry_delta] - [
u64:htlc_minimum_msat] - [
u32:fee_base_msat] - [
u32:fee_proportional_millionths] - [
u64:htlc_maximum_msat]
- [
channel_flags 位域用于指示通道的方向:它
标识此更新的原始节点并发出有关
通道的各种选项的信号。下表指定了其
各个位的含义:
| 位的位置 | 名称 | 含义 |
|---|---|---|
| 0 | direction |
此更新引用的方向。 |
| 1 | disable |
禁用通道。 |
message_flags 位域用于提供有关消息的更多详细信息:
| 位的位置 | 名称 |
|---|---|
| 0 | must_be_one |
| 1 | dont_forward |
签名验证的 node_id 取自相应的
channel_announcement:如果标志的最低有效位为 0,则为 node_id_1,
否则为 node_id_2。
要求
原始节点:
- 在收到
channel_ready之前,不得发送创建的channel_update。 - 可以创建
channel_update以将通道参数传达给 通道对等方,即使尚未宣布该通道(即 未设置announce_channel位,或者在 对等方交换公告签名之前发送了channel_update)。- 必须将
short_channel_id设置为从对等方收到的alias,或真实的通道short_channel_id。 - 必须在
message_flags中将dont_forward设置为 1 - 由于隐私原因,不得将此类
channel_update转发给其他对等方。 - 注意:这种
channel_update,即没有先前的channel_announcement的通道更新,对任何其他对等方都无效,并且会被丢弃。
- 必须将
- 必须使用其自己的
node_id将signature设置为signature之后剩余的整个数据包的双 SHA256 的签名。 - 必须设置
chain_hash和short_channel_id以匹配在channel_announcement消息中唯一标识的通道的 32 字节哈希和 8 字节通道 ID。 - 如果原始节点是消息中的
node_id_1:- 必须将
channel_flags的direction位设置为 0。
- 必须将
- 否则:
- 必须将
channel_flags的direction位设置为 1。
- 必须将
- 必须将
htlc_maximum_msat设置为它将通过此通道发送的单个 HTLC 的最大值。- 必须将其设置为小于或等于通道容量。
- 必须将其设置为小于或等于从对等方收到的
max_htlc_value_in_flight_msat。 - 必须将其设置为大于或等于
htlc_minimum_msat。
- 必须在
message_flags中将must_be_one设置为 1。 - 必须将
channel_flags和message_flags中未分配含义的位设置为 0。 - 可以创建一个
disable位设置为 1 的channel_update并发送,以 指示通道的临时不可用(例如,由于失去 连接)或永久不可用(例如,在链上结算之前)。- 可以发送一个
disable位设置为 0 的后续channel_update,以 重新启用通道。
- 可以发送一个
- 必须将
timestamp设置为大于 0,并且大于此short_channel_id以前发送的任何channel_update。- 应该将
timestamp基于 UNIX 时间戳。
- 应该将
- 必须将
cltv_expiry_delta设置为它将从传入 HTLC 的cltv_expiry中减去的区块数。 - 必须将
htlc_minimum_msat设置为通道对等方将接受的最小 HTLC 值(以毫聪)。- 必须将
htlc_minimum_msat设置为小于或等于htlc_maximum_msat。
- 必须将
- 必须将
fee_base_msat设置为它将对任何 HTLC 收取的 基本费用(以毫聪为单位)。 - 必须将
fee_proportional_millionths设置为它将对每个转移的 聪收取的金额(以百万分之一聪为单位)。 - 不应创建冗余的
channel_update - 如果它创建了一个具有更新的通道参数的新
channel_update:- 应该继续接受之前的通道参数 10 分钟
接收节点:
- 如果
short_channel_id与之前的channel_announcement不匹配, 或者如果通道在此期间已关闭:- 必须忽略与它自己的
通道不对应的
channel_update。
- 必须忽略与它自己的
通道不对应的
- 应该接受其自己的通道的
channel_update(即使是非公开的), 以便了解相关的原始节点的转发参数。 - 如果
signature不是有效签名,则使用node_id的 此消息之后整个消息的双 SHA256(包括fee_proportional_millionths之后的未知字段):- 应该发送
warning并关闭连接。 - 不得进一步处理该消息。
- 应该发送
- 如果指定的
chain_hash值未知(意味着它在 指定的链上不活动):- 必须忽略通道更新。
- 如果
timestamp等于此short_channel_id和node_id的 上次收到的channel_update:- 如果
timestamp下面的字段不同:- 可以将此
node_id列入黑名单。 - 可以忘记与其关联的所有通道。
- 可以将此
- 如果
timestamp下面的字段相等:- 应该忽略此消息
- 如果
- 如果
timestamp低于此short_channel_id和node_id的 上次收到的channel_update:- 应该忽略该消息。
- 否则:
- 如果
timestamp在未来太不合理了:- 可以丢弃
channel_update。
- 可以丢弃
- 否则:
- 应该将消息排队以进行重播。
- 可以选择不处理长于预期最小长度的消息。
- 如果
- 如果
htlc_maximum_msat<htlc_minimum_msat:- 应该在路由考虑因素中忽略此通道。
- 如果
htlc_maximum_msat大于通道容量:- 可以将此
node_id列入黑名单 - 应该在路由考虑因素中忽略此通道。
- 可以将此
- 否则:
- 应该在路由时考虑
htlc_maximum_msat。
- 应该在路由时考虑
理由
节点使用 timestamp 字段来修剪在未来太远或两周内未更新的 channel_update;因此,
将其设置为 UNIX 时间戳(即自 UTC 1970-01-01 以来的秒数)是有意义的。
但是,这不能成为硬性要求,因为在单秒内可能有两个 channel_update。
假设在同一秒内更改通道
参数的一个以上的 channel_update 消息可能是 DoS 攻击,因此,
可能会将负责签名此类消息的节点列入黑名单。但是,节点可以使用不同的签名发送相同的
channel_update 消息(更改签名中的 nonce 签名),因此会检查签名之外的字段,以查看
是否为同一时间戳更改了通道参数。同样重要的是要注意,
ECDSA 签名是可延展的。因此,接收到 channel_update
消息的中间节点仅通过更改签名的 s 组件即可重新广播它,且签名为-s。
但是,这不应导致消息源自的 node_id 被列入黑名单。
针对冗余 channel_update 的建议最大限度地减少了垃圾邮件发送到网络,
但是有时这是不可避免的。例如,与
无法访问的对等方的通道最终将导致 channel_update 指示该通道已禁用,
而当对等方重新建立联系时,另一个更新会重新启用该通道。因为 gossip
消息是批量处理的并且替换先前的消息,所以结果可能是单个看似冗余
的更新。
当节点创建一个新的 channel_update 以更改其通道参数时,
它将需要一些时间才能在网络中传播,并且付款人可能会使用
较旧的参数。建议至少保留 10 分钟的较旧参数,以提高付款
延迟和可靠性。
message_flags 中的 must_be_one 字段以前用于指示
htlc_maximum_msat 字段的存在。现在必须始终存在此字段,因此 must_be_one 是一个常量值,接收者会忽略它。
查询消息
过去,对消息的理解是由 gossip_queries 功能位指示的;现在这些消息得到了普遍支持,该功能现在已被稍微重新调整用途。不提供此功能意味着节点不值得查询 gossip:它们要么不存储整个 gossip 地图,要么仅连接到单个对等方(这一个)。
有几个消息包含一个长 short_channel_id 数组(称为 encoded_short_ids),因此我们包含一个编码字节,如果它们提供好处,则允许将来定义不同的编码方案。
编码类型:
0:按升序排列的short_channel_id类型的未压缩数组。1:以前用于 zlib 压缩,不得使用此编码。
此编码也用于其他类型的数组(时间戳、标志等),
并使用 encoded_ 前缀指定。例如,encoded_timestamps 是带有 0 前缀的时间戳数组。
查询消息可以使用可选字段进行扩展,这些字段可以通过以下方式帮助减少同步路由表所需的消息数量:
- 基于时间戳过滤
channel_update消息:只请求比你已有的更新的channel_update消息。 - 基于校验和过滤
channel_update消息:只请求与你已有的信息不同的channel_update消息。
节点可以使用 gossip_queries_ex feature bit 来表示它们支持扩展的gossip查询。
query_short_channel_ids/reply_short_channel_ids_end 消息
-
类型: 261 (
query_short_channel_ids) -
数据:
- [
chain_hash:chain_hash] - [
u16:len] - [
len*byte:encoded_short_ids] - [
query_short_channel_ids_tlvs:tlvs]
- [
-
tlv_stream:query_short_channel_ids_tlvs -
类型:
- 类型: 1 (
query_flags) - 数据:
- [
byte:encoding_type] - [
...*byte:encoded_query_flags]
- [
- 类型: 1 (
encoded_query_flags 是一个位域数组,每个位域一个 bigsize,每个 short_channel_id 一个位域。这些位的含义如下:
| 位位置 | 含义 |
|---|---|
| 0 | 发送者想要 channel_announcement |
| 1 | 发送者想要节点 1 的 channel_update |
| 2 | 发送者想要节点 2 的 channel_update |
| 3 | 发送者想要节点 1 的 node_announcement |
| 4 | 发送者想要节点 2 的 node_announcement |
查询标志必须以最小方式编码,这意味着一个标志将用单个字节编码。
- 类型: 262 (
reply_short_channel_ids_end) - 数据:
- [
chain_hash:chain_hash] - [
byte:full_information]
- [
这是一个通用机制,允许节点查询特定通道的 channel_announcement 和 channel_update 消息(通过 short_channel_id 标识)。这通常用于节点看到其没有 channel_announcement 的 channel_update,或者因为它从 reply_channel_range 获得了先前未知的 short_channel_id。
要求
发送者:
- 不应该将其发送给不提供
gossip_queries的对等方。 - 如果它已向该对等方发送了先前的
query_short_channel_ids并且未收到reply_short_channel_ids_end,则不得发送query_short_channel_ids。 - 必须将
chain_hash设置为唯一标识short_channel_id所引用的链的 32 字节哈希。 - 必须将
encoded_short_ids的第一个字节设置为编码类型。 - 必须将整数个
short_channel_id编码为encoded_short_ids - 如果它收到其没有
channel_announcement的short_channel_id的channel_update,则可以发送此消息。 - 如果所引用的通道不是未花费的输出,则不应发送此消息。
- 可以包含可选的
query_flags。如果是这样:- 必须像
encoded_short_ids一样设置encoding_type。 - 每个查询标志都是最小编码的 bigsize。
- 必须为每个
short_channel_id编码一个查询标志。
- 必须像
接收者:
- 如果
encoded_short_ids的第一个字节不是已知的编码类型:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 如果
encoded_short_ids未解码为整数个short_channel_id:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 如果它尚未向来自该发送者的先前收到的
query_short_channel_ids发送reply_short_channel_ids_end:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 如果传入消息包含
query_short_channel_ids_tlvs:- 如果
encoding_type不是已知的编码类型:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 如果
encoded_query_flags未解码为每个short_channel_id恰好一个标志:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 如果
- 必须响应每个已知的
short_channel_id:- 如果传入消息不包含
encoded_query_flags:- 使用
channel_announcement和每个端的最新channel_update - 必须在每个
channel_announcement之后跟随任何node_announcement
- 使用
- 否则:
- 我们将
encoded_short_ids中第 N 个short_channel_id的query_flag定义为解码后的encoded_query_flags的第 N 个 bigsize。 - 如果设置了
query_flag的第 0 位:- 必须回复
channel_announcement
- 必须回复
- 如果设置了
query_flag的第 1 位,并且它已收到来自node_id_1的channel_update:- 必须回复
node_id_1的最新channel_update
- 必须回复
- 如果设置了
query_flag的第 2 位,并且它已收到来自node_id_2的channel_update:- 必须回复
node_id_2的最新channel_update
- 必须回复
- 如果设置了
query_flag的第 3 位,并且它已收到来自node_id_1的node_announcement:- 必须回复
node_id_1的最新node_announcement
- 必须回复
- 如果设置了
query_flag的第 4 位,并且它已收到来自node_id_2的node_announcement:- 必须回复
node_id_2的最新node_announcement
- 必须回复
- 我们将
- 不应等待下一次传出 gossip 刷新来发送这些消息。
- 如果传入消息不包含
- 应该避免发送重复的
node_announcements以响应单个query_short_channel_ids。 - 必须在这些响应之后跟随
reply_short_channel_ids_end。 - 如果不维护
chain_hash的最新通道信息:- 必须将
full_information设置为 0。
- 必须将
- 否则:
- 应该将
full_information设置为 1。
- 应该将
理论依据
未来的节点可能没有完整的信息;他们肯定不会拥有关于未知 chain_hash 链的完整信息。虽然此 full_information 字段(以前且令人困惑地称为 complete)不可信,但 0 表示发送方应在其他地方搜索其他数据。
显式的 reply_short_channel_ids_end 消息意味着接收者可以指示它一无所知,并且发送者无需依赖超时。它还会导致查询的自然速率限制。
query_channel_range 和 reply_channel_range 消息
-
类型: 263 (
query_channel_range) -
数据:
- [
chain_hash:chain_hash] - [
u32:first_blocknum] - [
u32:number_of_blocks] - [
query_channel_range_tlvs:tlvs]
- [
-
tlv_stream:query_channel_range_tlvs -
类型:
- 类型: 1 (
query_option) - 数据:
- [
bigsize:query_option_flags]
- [
- 类型: 1 (
query_option_flags 是一个位域,表示为最小编码的 bigsize。这些位的含义如下:
| 位位置 | 含义 |
|---|---|
| 0 | 发送者想要时间戳 |
| 1 | 发送者想要校验和 |
虽然这是可能的,但要求校验和而不要求时间戳是没有什么用的:接收节点可能具有带有不同校验和的旧 channel_update,要求它是没有用的。并且如果 channel_update 校验和实际上是 0(这不太可能),则不会对其进行查询。
-
类型: 264 (
reply_channel_range) -
数据:
- [
chain_hash:chain_hash] - [
u32:first_blocknum] - [
u32:number_of_blocks] - [
byte:sync_complete] - [
u16:len] - [
len*byte:encoded_short_ids] - [
reply_channel_range_tlvs:tlvs]
- [
-
tlv_stream:reply_channel_range_tlvs -
类型:
- 类型: 1 (
timestamps_tlv) - 数据:
- [
byte:encoding_type] - [
...*byte:encoded_timestamps]
- [
- 类型: 3 (
checksums_tlv) - 数据:
- [
...*channel_update_checksums:checksums]
- [
- 类型: 1 (
对于单个 channel_update,时间戳编码为:
- 子类型:
channel_update_timestamps - 数据:
- [
u32:timestamp_node_id_1] - [
u32:timestamp_node_id_2]
- [
其中:
timestamp_node_id_1是node_id_1的channel_update的时间戳,如果该节点没有channel_update,则为 0。timestamp_node_id_2是node_id_2的channel_update的时间戳,如果该节点没有channel_update,则为 0。
对于单个 channel_update,校验和编码为:
- 子类型:
channel_update_checksums - 数据:
- [
u32:checksum_node_id_1] - [
u32:checksum_node_id_2]
- [
其中:
checksum_node_id_1是node_id_1的channel_update的校验和,如果该节点没有channel_update,则为 0。checksum_node_id_2是node_id_2的channel_update的校验和,如果该节点没有channel_update,则为 0。
channel_update 的校验和是 RFC3720 中指定的此 channel_update 的 CRC32C 校验和,不包括其 signature 和 timestamp 字段。
这允许查询特定块中的通道。
要求
query_channel_range 的发送者:
- 不应将其发送给不提供
gossip_queries的对等方。 - 如果它已向该对等方发送了先前的
query_channel_range并且未收到所有reply_channel_range回复,则不得发送此消息。 - 必须将
chain_hash设置为唯一标识它希望reply_channel_range引用的链的 32 字节哈希 - 必须将
first_blocknum设置为它想要知道通道的第一个块 - 必须将
number_of_blocks设置为 1 或更大。 - 可以附加一个额外的
query_channel_range_tlv,该 TLV 指定了它想要接收的扩展信息的类型。
query_channel_range 的接收者:
- 如果它尚未向来自该发送者的先前收到的
query_channel_range发送所有reply_channel_range:- 可以发送
warning。 - 可以关闭连接。
- 可以发送
- 必须回复一个或多个
reply_channel_range:- 必须将
chain_hash设置为与query_channel_range的chain_hash相等, - 必须将
number_of_blocks限制为结果可以容纳在encoded_short_ids中的最大块数 - 可以在多个
reply_channel_range中拆分块内容。 - 第一条
reply_channel_range消息:- 必须将
first_blocknum设置为小于或等于query_channel_range中的first_blocknum - 必须将
first_blocknum加上number_of_blocks设置为大于query_channel_range中的first_blocknum。
- 必须将
- 后续的
reply_channel_range消息:- 必须具有等于或大于先前的
first_blocknum的first_blocknum。
- 必须具有等于或大于先前的
- 如果这不是最终的
reply_channel_range,则必须将sync_complete设置为false。 - 最终的
reply_channel_range消息:- 必须使
first_blocknum加上number_of_blocks等于或大于query_channel_range的first_blocknum加上number_of_blocks。
- 必须使
- 必须将
sync_complete设置为true。
- 必须将
如果传入消息包含 query_option,则接收者可以向其回复附加额外信息:
- 如果设置了
query_option_flags中的第 0 位,则接收者可以附加一个timestamps_tlv,其中包含encoded_short_ids中所有short_channel_id的channel_update时间戳 - 如果设置了
query_option_flags中的第 1 位,则接收者可以附加一个checksums_tlv,其中包含encoded_short_ids中所有short_channel_id的channel_update校验和
理论依据
单个响应可能太大而无法容纳在单个数据包中,因此可能需要多个回复。 我们希望允许对等方存储(例如)1000 个块范围的预设结果,因此回复可以超出请求的范围。 但是,我们要求每个回复都相关(与请求的范围重叠)。
通过坚持要求回复按递增顺序排列,接收方可以轻松确定回复是否完成:只需检查 first_blocknum 加上 number_of_blocks 是否等于或超过其要求的 first_blocknum 加上 number_of_blocks。
时间戳和校验和字段的添加允许对等方省略查询冗余更新。
gossip_timestamp_filter 消息
- 类型: 265 (
gossip_timestamp_filter) - 数据:
- [
chain_hash:chain_hash] - [
u32:first_timestamp] - [
u32:timestamp_range]
- [
此消息允许节点将未来的gossip消息限制在特定范围内。 想要任何gossip消息的节点都必须发送此消息,否则将不会收到任何gossip消息。
请注意,此过滤器会替换任何先前的过滤器,因此可以多次使用它来更改来自对等方的gossip。
要求
发送者:
- 必须将
chain_hash设置为唯一标识它希望gossip引用的链的 32 字节哈希。 - 如果接收者不提供
gossip_queries:- 应该将
first_timestamp设置为 0xFFFFFFFF,并将timestamp_range设置为 0。
- 应该将
接收者:
- 应该发送所有
timestamp大于或等于first_timestamp且小于first_timestamp加上timestamp_range的gossip消息。- 可以等待下一次传出gossip刷新来发送这些消息。
- 应该在生成gossip消息时发送这些消息,而不管
timestamp如何。 - 否则(中继的gossip):
- 应该将未来的gossip消息限制为
timestamp大于或等于first_timestamp且小于first_timestamp加上timestamp_range的消息。
- 应该将未来的gossip消息限制为
- 如果
channel_announcement没有相应的channel_update:- 必须不发送
channel_announcement。
- 必须不发送
- 否则:
- 必须将
channel_announcement的timestamp视为相应channel_update的timestamp。 - 必须在收到第一个相应的
channel_update之后考虑是否发送channel_announcement。
- 必须将
- 如果发送了
channel_announcement:- 必须在任何相应的
channel_update和node_announcement之前发送channel_announcement。
- 必须在任何相应的
理论依据
由于 channel_announcement 没有时间戳,我们生成一个可能的时间戳。 如果没有 channel_update,则根本不会发送它,这在修剪通道的情况下最有可能发生。
否则,channel_announcement 通常会立即跟随 channel_update。 理想情况下,我们将指定使用第一个(最旧的)channel_update 的时间戳作为 channel_announcement 的时间,但是网络上的新节点将没有此信息,并且还需要存储第一个 channel_update 时间戳。 相反,我们允许使用任何更新,这很容易实现。
在仍然缺少 channel_announcement 的情况下,可以使用 query_short_channel_ids 来检索它。
当节点有许多对等点时,可以使用 timestamp_filter 来减少其gossip负载(例如,在前几个对等点之后将 first_timestamp 设置为 0xFFFFFFFF,假设传播足够)。 这种关于足够传播的假设不适用于节点本身直接生成的gossip消息,因此它们应忽略过滤器。
要求
一个节点:
- 在没有明确要求的情况下,不得中继它自己未生成的任何gossip消息。
重新广播
要求
接收节点:
- 收到新的
channel_announcement或带有更新的timestamp的channel_update或node_announcement时:- 应该相应地更新其网络的本地视图。
- 应用公告中的更改后:
- 如果没有与相应的始发节点关联的通道:
- 可以从其已知节点集中清除始发节点。
- 否则:
- 应该更新适当的元数据 并且 存储与公告关联的签名。
- 注意:这将稍后允许节点为其对等方重建公告。
- 应该更新适当的元数据 并且 存储与公告关联的签名。
- 如果没有与相应的始发节点关联的通道:
一个节点:
- 在收到
gossip_timestamp_filter之前,不得发送它自己未生成的gossip。 - 应该每 60 秒刷新一次传出的gossip消息,而与消息的到达时间无关。
- 注意:这会导致交错的公告是唯一的(未复制)。
- 不应将gossip消息转发给在
init中发送networks并且未指定此gossip消息的chain_hash的对等方。
- 可以定期重新宣布其通道。
- 注意:不鼓励这样做,以保持资源需求较低。
理论依据
处理完gossip消息后,它将被添加到传出消息列表中,该列表的目标是处理节点的对等方,从而替换始发节点的任何较旧的更新。 此gossip消息列表将定期刷新; 这种存储和延迟转发广播称为交错广播。 同样,这种批处理形成了一种自然速率限制,且开销较低。
HTLC费用
要求
始发节点:
- 应该接受支付的费用等于或大于以下值的 HTLC:
- fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
- 在发送
channel_update之后的合理时间内,应该接受支付较旧费用的 HTLC。- 注意:这允许任何传播延迟。
修剪网络视图
要求
一个节点:
- 应该监视区块链中的 funding 交易,以识别正在关闭的通道。
- 如果通道的 funding 输出已花费并收到 12 个区块确认:
- 应该从本地网络视图中删除 且 被视为已关闭。
- 如果宣布的节点不再具有任何关联的开放通道:
- 可以从其本地视图中修剪通过
node_announcement消息添加的节点。- 注意:这是
node_announcement依赖于channel_announcement的直接结果。
- 注意:这是
- 可以从其本地视图中修剪通过
关于修剪过时条目的建议
要求
一个节点:
- 如果任何一个方向上最新的
channel_update的timestamp已经超过两周(1209600 秒):- 可以修剪该通道。
- 可以忽略该通道。
- 注意:这是一个单独的节点策略,不得 由转发对等方强制执行,例如,通过在接收到过时的gossip消息时关闭通道。
理论依据
几种情况可能导致通道变得不可用,并且其端点无法发送这些通道的更新。 例如,如果两个端点都无法访问其私钥,并且既无法签署 channel_update 也无法在链上关闭通道,则会发生这种情况。 在这种情况下,通道不太可能成为计算出的路线的一部分,因为它们将与网络的其余部分分隔开; 但是,它们将保留在本地网络视图中,并将无限期地转发给其他对等方。
最早的 channel_update 用于修剪通道,因为双方都需要处于活动状态,通道才能可用。 这样做即使在一侧继续发送新的 channel_update 但另一侧已消失的情况下,也可以修剪通道。
路由建议
在计算 HTLC 的路线时,需要同时考虑 cltv_expiry_delta 和费用:cltv_expiry_delta 会增加在最坏情况下的资金不可用时间。 这两个属性之间的关系尚不清楚,因为它取决于所涉及节点的可靠性。
如果通过简单地路由到预期的接收者并对 cltv_expiry_delta 求和来计算路线,则中间节点可能会猜测它们在路线中的位置。 知道 HTLC 的 CLTV、周围的网络拓扑和 cltv_expiry_delta 使攻击者可以猜测预期的接收者。 因此,非常希望在预期接收方将收到的 CLTV 中添加一个随机偏移量,这会增加沿途所有 CLTV。
为了创建一个合理的偏移量,始发节点 可以 在图上启动一个有限的随机游走,从预期的接收者开始并对 cltv_expiry_delta 求和,并将结果总和用作偏移量。 这有效地创建了实际路线的影子路线扩展,并提供了比简单地选择随机偏移量更好的针对此攻击向量的保护。
其他更高级的考虑因素包括路线选择的多样化,以避免单点故障和检测,以及本地通道的平衡。
路由示例
考虑四个节点:
B
/ \
/ \
A C
\ /
\ /
D
每个节点在其每个通道的末尾都公告以下 cltv_expiry_delta:
- A:10 个区块
- B:20 个区块
- C:30 个区块
- D:40 个区块
C 在请求付款时也使用 18(默认值)的 min_final_cltv_expiry_delta。
同样,每个节点都有一套用于每个通道的既定费用方案:
- A:100 base + 1000 百万分之一
- B:200 base + 2000 百万分之一
- C:300 base + 3000 百万分之一
- D:400 base + 4000 百万分之一
网络将看到八个 channel_update 消息:
- A->B:
cltv_expiry_delta= 10,fee_base_msat= 100,fee_proportional_millionths= 1000 - A->D:
cltv_expiry_delta= 10,fee_base_msat= 100,fee_proportional_millionths= 1000 - B->A:
cltv_expiry_delta= 20,fee_base_msat= 200,fee_proportional_millionths= 2000 - D->A:
cltv_expiry_delta= 40,fee_base_msat= 400,fee_proportional_millionths= 4000 - B->C:
cltv_expiry_delta= 20,fee_base_msat= 200,fee_proportional_millionths= 2000 - D->C:
cltv_expiry_delta= 40,fee_base_msat= 400,fee_proportional_millionths= 4000 - C->B:
cltv_expiry_delta= 30,fee_base_msat= 300,fee_proportional_millionths= 3000 - C->D:
cltv_expiry_delta= 30,fee_base_msat= 300,fee_proportional_millionths= 3000
B->C. 如果 B 要将 4,999,999 毫聪直接发送到 C,它既不会收取自己的费用,也不会添加自己的 cltv_expiry_delta,因此它将使用 C 请求的 min_final_cltv_expiry_delta 18。 据推测,它还会添加一个 影子路线 以提供额外的 CLTV 42。 此外,它可以在其他跳添加额外的 CLTV delta,因为这些值表示最小值,但为了简单起见,这里选择不这样做:
amount_msat: 4999999cltv_expiry: current-block-height + 18 + 42onion_routing_packet:amt_to_forward= 4999999outgoing_cltv_value= current-block-height + 18 + 42
A->B->C. 如果 A 要通过 B 将 4,999,999 毫聪发送到 C,则需要支付 B 在 B->C channel_update 中指定的费用,该费用按照 HTLC 费用 计算:
fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
200 + ( 4999999 * 2000 / 1000000 ) = 10199
同样,它还需要添加 B->C 的 channel_update cltv_expiry_delta (20)、C 请求的 min_final_cltv_expiry_delta (18) 和 影子路线的成本 (42)。 因此,A->B 的 update_add_htlc 消息将是:
amount_msat: 5010198cltv_expiry: current-block-height + 20 + 18 + 42onion_routing_packet:amt_to_forward= 4999999outgoing_cltv_value= current-block-height + 18 + 42
B->C 的 update_add_htlc 将与上面 B->C 的直接付款相同。
A->D->C. 最后,如果由于某种原因 A 选择了通过 D 的更昂贵的路线,则 A->D 的 update_add_htlc 消息将是:
amount_msat: 5020398cltv_expiry: current-block-height + 40 + 18 + 42onion_routing_packet:amt_to_forward= 4999999outgoing_cltv_value= current-block-height + 18 + 42
D->C 的 update_add_htlc 将再次与上面 B->C 的直接付款相同。
<br>
本作品已获得 Creative Commons Attribution 4.0 International License 的许可。
- 原文链接: github.com/lightning/bol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~