BOLT 7:P2P 节点和通道发现

  • lightning
  • 发布于 2025-02-12 12:42
  • 阅读 14

本文档描述了简单的节点发现、通道发现和通道更新机制,这些机制不依赖于第三方来传播信息。为了支持通道和节点发现,支持三种 gossip 消息:node_announcement、channel_announcement 和 channel_update。文章还详细介绍了这些消息的格式、要求以及路由推荐。

BOLT #7: P2P 节点和通道发现

本规范描述了简单的节点发现、通道发现和通道更新机制,这些机制不依赖于第三方来传播信息。

节点和通道发现服务于两个不同的目的:

  • 节点发现允许节点广播它们的 ID、主机和端口,以便其他节点可以打开连接并与它们建立支付通道。
  • 通道发现允许创建和维护网络拓扑的本地视图,以便节点可以发现到达所需目的地的路由。

为了支持通道和节点发现,支持三个 gossip messages

  • 对于节点发现,节点交换 node_announcement 消息,该消息提供有关节点的其他信息。可能存在 多个 node_announcement 消息,以便更新节点信息。

    • 对于通道发现,网络中的节点会交换 channel_announcement 消息,其中包含有关两个节点之间新 通道的信息。他们还可以交换 channel_update 消息,以更新有关通道的信息。对于任何通道,只能有一个有效的 channel_announcement,但至少需要两个 channel_update 消息。

目录

short_channel_id 的定义

short_channel_id 是 funding transaction 的唯一描述。 它的构造方式如下:

  1. 最高有效 3 字节:指示区块高度
  2. 接下来的 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 消息所需的签名。

  1. type: 259 (announcement_signatures)
  2. data:
    • [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_idbitcoin_key 匹配的密钥对其进行签名。签名后,可以发送 announcement_signatures 消息。

一个节点:

  • 如果 open_channel 消息设置了 announce_channel 位 并且尚未发送 shutdown 消息:
    • 必须发送 announcement_signatures 消息。
      • 在发送和接收到 funding_locked 并且 funding transaction 至少有六个确认之前,不得发送 announcement_signatures 消息。
  • 否则:
    • 不得发送 announcement_signatures 消息。
  • 重新连接后(一旦满足上述时间要求):
    • 必须使用其自己的 announcement_signatures 消息来响应第一个 announcement_signatures 消息。
    • 如果它尚未收到 announcement_signatures 消息:
      • 应该重新传输 announcement_signatures 消息。

接收节点:

  • 如果 short_channel_id 不正确:
    • 应该发送一个 warning 并关闭连接,或者发送一个 error 并使通道失败。
  • 如果 node_signaturebitcoin_signature 不正确:
    • 可以发送一个 warning 并关闭连接,或者发送一个 error 并使通道失败。
  • 如果它已经发送和接收到有效的 announcement_signatures 消息:
    • 应该将其对等方排队 channel_announcement 消息。
  • 如果它尚未发送 funding_locked:
    • 可以推迟处理 announcement_signatures,直到它发送 funding_locked 之后
    • 否则:
      • 必须忽略它。

理由

允许推迟过早的 announcement_signatures 的原因是 该规范的早期版本不需要等待收到 funding locked:推迟而不是忽略它允许与 此行为兼容。

channel_announcement 消息

gossip message 包含有关通道的所有权信息。它将 每个链上比特币密钥与关联的闪电网络节点密钥联系起来,反之亦然。 在至少一方使用 channel_update 宣布其费用水平和到期时间之前,该通道实际上不可用。

证明 node_1node_2 之间存在通道需要:

  1. 证明 funding transaction 支付给 bitcoin_key_1bitcoin_key_2
  2. 证明 node_1 拥有 bitcoin_key_1
  3. 证明 node_2 拥有 bitcoin_key_2

假设所有节点都知道未花费的交易输出,则第一个证明是 通过节点查找由 short_channel_id 给出的输出并 验证它是否确实是 BOLT #3 中指定的那些密钥的 P2WSH funding transaction 输出来实现的。

最后两个证明是通过显式签名来实现的: 为每个 bitcoin_key 生成 bitcoin_signature_1bitcoin_signature_2,并对每个对应的 node_id 进行签名。

还需要证明 node_1node_2 都同意 announcement message:这是通过拥有来自每个 node_id 的签名(node_signature_1node_signature_2)来签署消息来实现的。

  1. type: 256 (channel_announcement)
  2. data:
    • [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 字节哈希值:
    • 对于 Bitcoin blockchain
      • 必须将 chain_hash 值(以十六进制编码)设置为等于 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
  • 必须设置 short_channel_id 以引用已确认的 funding transaction, 如 BOLT #2 中所述。
    • 注意:相应的输出必须是 P2WSH,如 BOLT #3 中所述。
  • 必须将 node_id_1node_id_2 设置为操作该通道的两个节点的公钥, 使得 node_id_1 是按升序字典顺序排序的两个压缩密钥中按字典顺序较小的。
  • 必须将 bitcoin_key_1bitcoin_key_2 设置为 node_id_1node_id_2 各自的 funding_pubkey
  • 必须计算消息的 double-SHA256 哈希 h,从偏移量 256 开始,直到消息结束。
    • 注意:哈希跳过 4 个签名,但哈希消息的其余部分, 包括附加到末尾的任何未来字段。
  • 必须将 node_signature_1node_signature_2 设置为哈希 h 的有效 签名(使用 node_id_1node_id_2 各自的 密钥)。
  • 必须将 bitcoin_signature_1bitcoin_signature_2 设置为哈希 h 的有效 签名(使用 bitcoin_key_1bitcoin_key_2 各自的 密钥)。
  • 必须根据为此通道协商的功能设置 features,如 BOLT #9 中所述
  • 必须将 len 设置为保存其设置的 features 位所需的最小长度。

接收节点:

  • 必须通过验证 签名来验证消息的完整性和真实性。
  • 如果 features 字段中存在未知的偶数位:
    • 不得尝试通过该通道路由消息。
  • 如果 short_channel_id 的输出不对应于 P2WSH(使用 bitcoin_key_1bitcoin_key_2,如 BOLT #3 中所述)或者输出已 花费:
    • 必须忽略该消息。
  • 如果接收方不知道指定的 chain_hash
    • 必须忽略该消息。
  • 否则:
    • 如果 bitcoin_signature_1bitcoin_signature_2node_signature_1node_signature_2 无效或不正确:
      • 应该发送一个 warning
      • 可以关闭连接。
      • 必须忽略该消息。
    • 否则:
      • 如果 node_id_1node_id_2 已被列入黑名单:
      • 应该忽略该消息。
      • 否则:
      • 如果先前未将所引用的交易宣布为 通道:
        • 应该将该消息排队以进行重新广播。
        • 可以选择不排队长度超过最小预期 长度的消息。
      • 如果它先前已收到有效的 channel_announcement,对于 相同的交易,在同一个区块中,但对于不同的 node_id_1node_id_2
      • 应该将先前消息的 node_id_1node_id_2 列入黑名单, 以及此 node_id_1node_id_2,并忘记与它们连接的任何通道。
      • 否则:
      • 应该存储此 channel_announcement
  • 一旦其 funding 输出已花费或重组:
    • 应该忘记通道。

理由

两个节点都需要签名以表明它们愿意通过此通道路由其他 付款(即成为公共网络的一部分);要求它们的 比特币签名证明它们控制该通道。

将冲突节点列入黑名单不允许进行多个不同的 公告。任何节点都不应广播此类冲突公告,因为这表明密钥已泄露。

虽然不应在通道足够深之前对其进行广告,但 对重新广播的要求仅适用于交易尚未转移到 不同区块的情况。

为了避免存储过大的消息,但仍然允许合理的未来扩展,允许节点限制重新广播(可能在统计上)。

未来可能会出现新的通道功能:向后兼容(或 可选)功能将具有 奇数 功能位,而不兼容的功能将具有 偶数 功能位 ("It's OK to be odd!")。

node_announcement 消息

gossip message 允许节点指示与其关联的额外数据, 除了其公钥之外。为了避免微不足道的拒绝服务攻击, 不与已知通道关联的节点将被忽略。

  1. type: 257 (node_announcement)
  2. data:
    • [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_coloralias 允许情报部门为 节点分配颜色,例如黑色,以及诸如“IRATEMONK”和“WISTFULTOLL”之类的酷炫绰号。

addresses 允许节点宣布其接受传入网络 连接的意愿:它包含一系列用于连接到 节点的 address descriptor。第一个字节描述了地址类型,后跟 该类型的适当数量的字节。

定义了以下 address descriptor 类型:

  • 1: ipv4; data = [4:ipv4_addr][2:port] (length 6)
  • 2: ipv6; data = [16:ipv6_addr][2:port] (length 18)
  • 3: 已弃用(长度 12)。用于包含 Tor v2 onion services。
  • 4: Tor v3 onion service; data = [35:onion_addr][2:port] (length 37)
    • version 3 (prop224) onion service addresses; Encodes: [32:32_byte_ed25519_pubkey] || [2:checksum] || [1:version], where checksum = sha3(".onion checksum" | pubkey || version)[:2].

要求

始发节点:

  • 必须将 timestamp 设置为大于先前创建的任何 node_announcement 的时间戳。
    • 可以将其基于 UNIX 时间戳。
  • 必须将 signature 设置为 signature 之后整个 剩余数据包的 double-SHA256 的签名(使用 node_id 给出的密钥)。
  • 可以设置 aliasrgb_color 以自定义其在地图和 图表中的外观。
    • 注意:rgb_color 的第一个字节是红色值,第二个字节是 绿色值,最后一个字节是蓝色值。
  • 必须将 alias 设置为有效的 UTF-8 字符串,任何 alias 尾随字节 都等于 0。
  • 应该使用 addresses 填充每个期望传入连接的公共网络 地址的地址描述符。
  • 必须将 addrlen 设置为 addresses 中的字节数。
  • 必须按升序排列地址描述符。
  • 不应在任何位置放置任何零类型地址描述符。
  • 应该仅将放置用于校准 addresses 之后的字段。
  • 不得创建 port 等于 0 的 type 1type 2 地址描述符。
  • 应该确保 ipv4_addripv6_addr 是可路由的地址。
  • 必须根据 BOLT #9 设置 features
  • 应该将 flen 设置为保存其设置的 features 位所需的最小长度。
  • 不应宣布 Tor v2 onion service。

接收节点:

  • 如果 node_id 不是有效的压缩公钥:
    • 应该发送一个 warning
    • 可以关闭连接。
    • 必须不再处理该消息。
  • 如果 signature 不是有效签名(使用 node_id 对双重SHA256 进行签名,整个消息在 signature 字段之后,包括附加到末尾的任何未来字段):
    • 应该发送一个 warning
    • 可以关闭连接。
    • 必须不再处理该消息。
  • 如果 features 字段包含 未知偶数位:
    • 不应连接到该节点。
    • 除非支付 BOLT #11 invoice 中没有 设置相同的位,否则_不得_尝试将付款发送_到_该节点。
    • _不得_通过该节点_路由_付款。
  • 应该忽略与上面定义的类型不匹配的第一个 address descriptor
  • 如果 addrlen 不足以容纳已知类型的地址描述符:
    • 应该发送一个 warning
    • 可以关闭连接。
  • 如果 port 等于 0:
    • 应该忽略 ipv6_addripv4_addr
  • 如果 node_id 以前未知来自 channel_announcement 消息, 或者如果 timestamp 不大于从此 node_id 收到的最后一个 node_announcement
    • 应该忽略该消息。
  • 否则:
    • 如果 timestamp 大于从此 node_id 收到的最后一个 node_announcement
      • 应该将该消息排队以进行重新广播。
      • 可以选择不排队长度超过最小预期长度的消息。
  • 可以使用 rgb_coloralias 来引用接口中的节点。
    • 应该暗示它们的自签名来源。
  • 应该忽略 Tor v2 onion service。

理由

未来可能会出现新的节点功能:向后兼容(或 可选)的功能将具有 奇数 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 message 仅在 _中继_付款的上下文中才有用,而_发送_付款则不然。在进行付款时 A -> B -> C -> D,只有与 B 发起的 B -> C(由 B 宣布)通道和 C 发起的 C -> D(由 C 宣布)通道相关的 channel_update 才会生效。构建路由时,需要从目的地到源头向后计算 HTLC 的金额和到期时间。 要在路由中的最后一个 HTLC 中使用的 amount_msat 的确切初始值和 cltv_expiry 的最小值在付款请求中提供 (参见 BOLT #11)。

  1. type: 258 (channel_update)
  2. data:
    • [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] (option_channel_htlc_max)

channel_flags 位字段用于指示通道的方向:它 标识此更新的来源节点,并发出有关 通道的各种选项的信号。下表指定了其 各个位的含义:

位位置 名称 含义
0 direction 此更新引用的方向。
1 disable 禁用通道。

message_flags 位字段用于指示 channel_update 消息中是否存在可选 字段:

位位置 名称 字段
0 option_channel_htlc_max htlc_maximum_msat

请注意,htlc_maximum_msat 字段在当前 协议的生命周期内是静态的:它 _不_设计用于 指示每个方向上的实时通道容量,这将 既是大量数据泄漏,又是无用地垃圾邮件网络( gossip 平均需要 30 秒才能传播每个跃点)。

用于签名验证的 node_id 取自相应的 channel_announcement:如果标志的最低有效位为 0,则为 node_id_1,否则为 node_id_2

要求

始发节点:

  • 在收到 funding_locked 之前,不得发送创建的 channel_update
  • 可以创建一个 channel_update 以将通道参数通信给 通道对等方,即使尚未公告该通道(即未设置 announce_channel 位)。
    • 出于隐私 原因,不得将此类 channel_update 转发给其他对等方。
    • 注意:此类 channel_update(未在 channel_announcement 之前)对任何其他对等方都无效,并且会被丢弃。
  • 必须使用其自己的 node_idsignature 设置为 signature 之后剩余的整个数据包的 double-SHA256 的签名。
  • 必须设置 chain_hashshort_channel_id 以匹配唯一标识 channel_announcement 消息中指定的通道的 32 字节哈希和 8 字节通道 ID。
  • 如果始发节点是消息中的 node_id_1
    • 必须将 channel_flagsdirection 位设置为 0。
  • 否则:
    • 必须将 channel_flagsdirection 位设置为 1。
  • 如果存在 htlc_maximum_msat 字段:
    • 必须将 message_flagsoption_channel_htlc_max 位设置为 1。
    • 必须将 htlc_maximum_msat 设置为其将通过此通道发送的单个 HTLC 的最大值。
      • 必须将其设置为小于或等于通道容量。
      • 必须将其设置为小于或等于它从对等方收到的 max_htlc_value_in_flight_msat
  • 否则:
    • 必须将 message_flagsoption_channel_htlc_max 位设置为 0。
  • 必须将 channel_flagsmessage_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 值(以毫聪为单位)。
  • 必须将 fee_base_msat 设置为它将对任何 HTLC 收取的基本费用(以毫聪为单位)。
  • 必须将 fee_proportional_millionths 设置为它将按每转移的聪收取的金额(以百万分之一聪为单位)。
  • 不应创建冗余的 channel_update

接收节点:

  • 如果 short_channel_id 与先前的 channel_announcement 不匹配, 或者如果通道已在此期间关闭:
    • 对于不与其自身 通道之一对应的 channel_update,必须忽略 channel_update
  • 应该接受其自身通道的 channel_update(即使是非公开的), 以便了解关联的起始节点的转发参数。
  • 如果 signature 不是有效签名,则使用双 SHA256 的 node_id 对签名字段后的整个消息进行签名( 包括 fee_proportional_millionths 之后的未知字段):
    • 应该发送 warning 并关闭连接。
    • 必须不再处理该消息。
  • 如果指定的 chain_hash 值未知(意味着它在指定的链上不活跃):
    • 必须忽略通道更新。
  • 如果 timestamp 等于此 short_channel_idnode_id 的最后一个收到的 channel_update
    • 如果 timestamp 下面的字段不同:
      • 可以将此 node_id 列入黑名单。
      • 可以忘记与其关联的所有通道。
    • 如果 timestamp 下面的字段相等:
      • 应该忽略此消息
  • 如果 timestamp 低于此 short_channel_idnode_id 的最后一个收到的 channel_update 的时间戳:
    • 应该忽略该消息。
  • 否则:
    • 如果 timestamp 在未来不合理地遥远:
      • 可以丢弃 channel_update
    • 否则:
      • 应该将该消息排队以进行重新广播。
      • 可以选择不排队长度超过最小预期长度的消息。
  • 如果 message_flagsoption_channel_htlc_max 位为 0:
    • 必须认为 htlc_maximum_msat 不存在。
  • 否则:
    • 如果 htlc_maximum_msat 不存在或大于通道容量:
      • 可以将此 node_id 列入黑名单
      • 在路由考虑期间,应该忽略此通道。
    • 否则:
      • 在路由时应该考虑 htlc_maximum_msat

理由

节点使用 timestamp 字段来修剪 channel_update,这些 channel_update 要么在未来太远,要么两周内没有更新;因此, 将其设置为 UNIX 时间戳(即自 UTC 1970-01-01 以来的秒数)是有意义的。但是,考虑到在 一秒钟内可能出现两个 channel_update,这不能成为硬性要求。

假设在同一秒内多次更改通道参数的 channel_update 消息可能是 DoS 尝试,因此,可以禁止负责对这些消息签名的节点。但是,节点可以发送具有不同签名的相同 channel_update 消息(更改签名中的 nonce),因此,除了签名之外,还会检查字段以查看通道参数是否已更改为同一时间戳。同样重要的是要注意,ECDSA 签名是可延展的。因此,收到 channel_update 消息的中间节点可以通过仅将签名的 s 分量更改为 -s 来重新广播它。但是,这不应导致始发消息的 node_id 被列入黑名单。

显式 option_channel_htlc_max 标志指示 htlc_maximum_msat 的存在(而不是通过消息长度暗示 htlc_maximum_msat)允许我们在未来使用不同的字段扩展 channel_update。由于 Bitcoin 中的通道限制为 2^32-1 毫聪,因此 htlc_maximum_msat 具有相同的限制。

不建议使用冗余的 channel_update,这会最大限度地减少垃圾邮件网络, 但这有时是不可避免的。例如,与 无法访问的对等方的通道最终将导致 channel_update 指示该通道已禁用,而当 对等方重新建立联系时,另一个更新将重新启用该通道。由于 gossip message 被批量处理并替换以前的 gossip message,因此结果可能是一个看似冗余的更新。

查询消息

通过 init 协商 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 功能位来指示它们支持扩展 gossip 查询。

query_short_channel_ids/reply_short_channel_ids_end 消息

  1. type: 261 (query_short_channel_ids) (gossip_queries)

  2. data:

    • [chain_hash:chain_hash]
    • [u16:len]
    • [len*byte:encoded_short_ids]
    • [query_short_channel_ids_tlvs:tlvs]
  3. tlv_stream: query_short_channel_ids_tlvs

  4. types:

    1. type: 1 (query_flags)
    2. data:
      • [byte:encoding_type]
      • [...*byte:encoded_query_flags]
encoded_query_flags 是一个位字段数组,每个 short_channel_id 一个 bigsize 位字段。位具有以下含义: Bit Position Meaning
0 Sender wants channel_announcement
1 Sender wants channel_update for node 1
2 Sender wants channel_update for node 2
3 Sender wants node_announcement for node 1
4 Sender wants node_announcement for node 2

查询标志必须进行最小编码,这意味着一个标志将用单个字节进行编码。

  1. type: 262 (reply_short_channel_ids_end) (gossip_queries)
  2. data:
    • [chain_hash:chain_hash]
    • [byte:full_information]

这是一种通用机制,允许节点查询特定通道的 channel_announcementchannel_update 消息(通过 short_channel_id 标识)。这通常用于节点看到它没有 channel_announcementchannel_update,或者因为它从 reply_channel_range 获得了以前未知的 short_channel_id

要求

发送方:

  • 如果它已向该对等方发送了先前的 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_announcementshort_channel_idchannel_update,则可以发送此消息。
  • 如果所引用的通道不是未花费的输出,则不应发送此消息。
  • 可以包含可选的 query_flags。如果是这样:
    • 必须设置 encoding_type,如 encoded_short_ids 一样。
    • 每个查询标志都是一个最小编码的 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_idquery_flag 定义为解码后的 encoded_query_flags 的第 N 个 bigsize。
      • 如果设置了 query_flag 的第 0 位:
      • 必须回复 channel_announcement
      • 如果设置了 query_flag 的第 1 位,并且它已收到来自 node_id_1channel_update
      • 必须回复 node_id_1 的最新 channel_update
      • 如果设置了 query_flag 的第 2 位,并且它已收到来自 node_id_2channel_update
      • 必须回复 node_id_2 的最新 channel_update
      • 如果设置了 query_flag 的第 3 位,并且它已收到来自 node_id_1node_announcement
      • 必须回复 node_id_1 的最新 node_announcement
      • 如果设置了 query_flag 的第 4 位,并且它已收到来自 node_id_2node_announcement
      • 必须回复 node_id_2 的最新 node_announcement
    • 不应等待下一次传出 gossip 刷新来发送这些。
  • 避免在响应单个 query_short_channel_ids 时发送重复的 node_announcements
  • 必须在这些响应之后发送 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_rangereply_channel_range 消息

  1. type: 263 (query_channel_range) (gossip_queries)

  2. data:

    • [chain_hash:chain_hash]
    • [u32:first_blocknum]
    • [u32:number_of_blocks]
    • [query_channel_range_tlvs:tlvs]
  3. tlv_stream: query_channel_range_tlvs

  4. types:

    1. type: 1 (query_option)
    2. data:
      • [bigsize:query_option_flags]

query_option_flags 是一个位域,表示为最小编码的 bigsize。各位具有以下含义:

Bit Position Meaning
0 Sender wants timestamps
1 Sender wants checksums

虽然这是可能的,但如果不要求提供时间戳也要求提供校验和,那就不会很有用:接收节点可能具有具有不同校验和的较旧的 channel_update,要求提供它是没有用的。并且如果 channel_update 校验和实际上为 0(这不太可能),则不会查询它。

  1. type: 264 (reply_channel_range) (gossip_queries)

  2. data:

    • [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]
  3. tlv_stream: reply_channel_range_tlvs

  4. types:

    1. type: 1 (timestamps_tlv)
    2. data:
      • [byte:encoding_type]
      • [...*byte:encoded_timestamps]
    3. type: 3 (checksums_tlv)
    4. data:
      • [...*channel_update_checksums:checksums]

对于单个 channel_update,时间戳编码为:

  1. subtype: channel_update_timestamps
  2. data:
    • [u32:timestamp_node_id_1]
    • [u32:timestamp_node_id_2]

其中:

  • timestamp_node_id_1node_id_1channel_update 的时间戳,如果该节点没有 channel_update,则为 0。
  • timestamp_node_id_2node_id_2channel_update 的时间戳,如果该节点没有 channel_update,则为 0。

对于单个 channel_update,校验和编码为:

  1. subtype: channel_update_checksums
  2. data:
    • [u32:checksum_node_id_1]
    • [u32:checksum_node_id_2]

其中:

  • checksum_node_id_1node_id_1channel_update 的校验和,如果该节点没有 channel_update,则为 0。
  • checksum_node_id_2node_id_2channel_update 的校验和,如果该节点没有 channel_update,则为 0。

channel_update 的校验和是 RFC3720 中指定的 CRC32C 校验和,不包括 signaturetimestamp 字段。

这允许查询特定块中的通道。

要求

query_channel_range 的发送者:

  • 如果它已向该对等方发送了先前的 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 的接收者:

  • 如果它尚未将所有 reply_channel_range 发送到来自此发送方的先前收到的 query_channel_range
    • 可以发送一个 warning
    • 可以关闭连接。
  • 必须回复一个或多个 reply_channel_range
    • 必须设置 chain_hash 等于 query_channel_rangechain_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_rangefirst_blocknum 加上 number_of_blocks
    • 必须sync_complete 设置为 true

如果传入消息包含 query_option,则接收者_可以_将附加信息附加到其回复中:

  • 如果设置了 query_option_flags 中的第 0 位,则接收者_可以_附加一个 timestamps_tlv,其中包含 encoded_short_ids 中所有 short_chanel_idchannel_update 时间戳
  • 如果设置了 query_option_flags 中的第 1 位,则接收者_可以_附加一个 checksums_tlv,其中包含 encoded_short_ids 中所有 short_chanel_idchannel_update 校验和
理由

单个响应可能对于单个数据包来说太大,因此可能需要多个回复。我们希望允许对等方存储(例如)1000 个块范围的预置结果,因此回复可以超出请求的范围。但是,我们要求每个回复都相关(与请求的范围重叠)。

通过坚持回复按递增顺序排列,接收者可以轻松确定回复是否完成:只需检查 first_blocknum 加上 number_of_blocks 是否等于或超过它所要求的 first_blocknum 加上 number_of_blocks

时间戳和校验和字段的添加允许对等方省略查询冗余更新。

gossip_timestamp_filter 消息

  1. type: 265 (gossip_timestamp_filter) (gossip_queries)
  2. data:
    • [chain_hash:chain_hash]
    • [u32:first_timestamp]
    • [u32:timestamp_range]

此消息允许节点将未来的 gossip 消息约束到特定范围。想要任何 gossip 消息的节点都必须发送此消息,否则 gossip_queries 协商意味着不会收到任何 gossip 消息。

请注意,此筛选器会替换任何先前的筛选器,因此可以多次使用它来更改来自对等方的 gossip。

要求

发送者:

  • 必须chain_hash 设置为唯一标识它希望 gossip 引用的链的 32 字节哈希值。

接收者:

  • 发送所有 timestamp 大于或等于 first_timestamp 且小于 first_timestamp 加上 timestamp_range 的 gossip 消息。
    • 可以等待下一次传出 gossip 刷新来发送这些。
  • 在生成 gossip 消息时发送它们,而不管 timestamp 如何。
  • 否则(中继 gossip):
    • 将未来的 gossip 消息限制为 timestamp 大于或等于 first_timestamp 且小于 first_timestamp 加上 timestamp_range 的消息。
  • 如果 channel_announcement 没有相应的 channel_update
    • 必须不发送该 channel_announcement
  • 否则:
    • 必须channel_announcementtimestamp 视为相应 channel_updatetimestamp
    • 必须在收到第一个相应的 channel_update 后考虑是否发送 channel_announcement
  • 如果发送了 channel_announcement
    • 必须在任何相应的 channel_updatenode_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 消息的初始同步,它将在 init 消息中通过一个特性标志进行标记(BOLT #9)。

请注意,如果通过 init 协商了 gossip_queries 特性,则 initial_routing_sync 特性将被覆盖(并且应被视为等于 0)。

请注意,gossip_queries 不适用于较旧的节点,因此 initial_routing_sync 的值对于控制与它们的交互仍然很重要。

要求

一个节点:

  • 如果协商了 gossip_queries 特性:
    • 必须不中继任何它自己没有生成的 gossip 消息,除非明确请求。
  • 否则:
    • 如果它需要对等方的路由状态的完整副本:
      • initial_routing_sync 标志设置为 1。
    • 在收到将 initial_routing_sync 标志设置为 1 的 init 消息后:
      • 为所有已知通道和节点发送 gossip 消息,就像它们刚刚收到一样。
    • 如果 initial_routing_sync 标志设置为 0,或者如果初始同步已完成:
      • 恢复正常操作,如以下重新广播部分中所述。

重新广播

要求

接收节点:

  • 在收到新的 channel_announcementchannel_update 或具有更新 timestampnode_announcement 后:
    • 相应地更新其本地网络拓扑视图。
  • 在应用公告中的更改后:
    • 如果没有与相应原始节点关联的通道:
      • 可以从其已知节点集中清除原始节点。
    • 否则:
      • 更新适当的元数据 并且 存储与该公告关联的签名。
      • 注意:这将在以后允许节点为其对等方重建公告。

一个节点:

  • 如果已协商 gossip_queries 功能:
    • 在收到 gossip_timestamp_filter 之前,必须不发送自己未生成的 gossip。
  • 每 60 秒刷新一次传出的 gossip 消息,而与消息的到达时间无关。
    • 注意:这会导致错开的公告,这些公告是唯一的(不是重复的)。
    • 不应将 gossip 消息转发给在 init 中发送 networks 并且未指定此 gossip 消息的 chain_hash 的对等方。
  • 可以定期重新公布其通道。
    • 注意:不鼓励这样做,以保持较低的资源需求。
  • 建立连接后:
    • 发送所有 channel_announcement 消息,然后发送最新的 node_announcement channel_update 消息。

理由

一旦 gossip 消息被处理,它就会被添加到传出消息列表中,这些消息将发送到处理节点的对等方,替换来自原始节点的任何较旧的更新。此 gossip 消息列表将定期刷新;这种存储和延迟转发广播称为 交错广播。此外,这种批处理形成了一种自然速率限制,且开销较低。

在重新连接时发送所有 gossip 消息很简单,但允许新节点进行引导,以及允许已离线一段时间的节点进行更新。gossip_queries 选项允许更精细的同步。

HTLC 费用

要求

原始节点:

  • 接受支付费用等于或大于以下金额的 HTLC:
    • fee_base_msat + ( amount_to_forward * fee_proportional_millionths / 1000000 )
  • 在发送 channel_update 之后,在一段合理的时间内接受支付旧费用的 HTLC。
    • 注意:这允许任何传播延迟。

修剪网络视图

要求

一个节点:

  • 监视区块链中的资金交易,以识别正在关闭的通道。
  • 如果正在花费通道的资金输出:
    • 从本地网络视图中删除 并且 将其视为已关闭。
  • 如果公布的节点不再具有任何关联的开放通道:
    • 可以 从其本地视图中修剪通过 node_announcement 消息添加的节点。
      • 注意:这是 node_announcement 依赖于它之前有一个 channel_announcement 的直接结果。

关于修剪陈旧条目的建议

Requirements

一个节点:

  • 如果通道的最旧的 channel_updatetimestamp 早于两周(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

  1. A:10 个区块
  2. B:20 个区块
  3. C:30 个区块
  4. D:40 个区块

C 在请求付款时也使用 9 的 min_final_cltv_expiry(默认值)。

此外,每个节点都有一套固定的费用方案,用于其每个通道:

  1. A:100 base + 1000 millionths
  2. B:200 base + 2000 millionths
  3. C:300 base + 3000 millionths
  4. D:400 base + 4000 millionths

网络将看到八个 channel_update 消息:

  1. A->B:cltv_expiry_delta = 10, fee_base_msat = 100, fee_proportional_millionths = 1000
  2. A->D:cltv_expiry_delta = 10, fee_base_msat = 100, fee_proportional_millionths = 1000
  3. B->A:cltv_expiry_delta = 20, fee_base_msat = 200, fee_proportional_millionths = 2000
  4. D->A:cltv_expiry_delta = 40, fee_base_msat = 400, fee_proportional_millionths = 4000
  5. B->C:cltv_expiry_delta = 20, fee_base_msat = 200, fee_proportional_millionths = 2000
  6. D->C:cltv_expiry_delta = 40, fee_base_msat = 400, fee_proportional_millionths = 4000
  7. C->B:cltv_expiry_delta = 30, fee_base_msat = 300, fee_proportional_millionths = 3000
  8. 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 9。据推测,它还会添加一个 影子路由 以提供额外的 CLTV 42。此外,它可以添加其他跳中的额外 CLTV delta,因为这些值表示最小值,但为了简单起见,这里选择不这样做:

  • amount_msat: 4999999
  • cltv_expiry: current-block-height + 9 + 42
  • onion_routing_packet:
    • amt_to_forward = 4999999
    • outgoing_cltv_value = current-block-height + 9 + 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 (9) 和 影子路由 的成本 (42)。因此,A->B 的 update_add_htlc 消息将是:

  • amount_msat: 5010198
  • cltv_expiry: current-block-height + 20 + 9 + 42
  • onion_routing_packet:
    • amt_to_forward = 4999999
    • outgoing_cltv_value = current-block-height + 9 + 42

B->C 的 update_add_htlc 将与上面 B->C 的直接付款相同。

A->D->C. 最后,如果由于某种原因 A 选择了通过 D 的更昂贵的路由,则 A->D 的 update_add_htlc 消息将是:

  • amount_msat: 5020398
  • cltv_expiry: current-block-height + 40 + 9 + 42
  • onion_routing_packet:
    • amt_to_forward = 4999999
    • outgoing_cltv_value = current-block-height + 9 + 42

D->C 的 update_add_htlc 将再次与上面 B->C 的直接付款相同。

![Creative Commons License](https://i.creativecommons.org/l/by/4.0/88x31.png "License CC-BY")
<br>
This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).
  • 原文链接: github.com/lightning/bol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
lightning
lightning
江湖只有他的大名,没有他的介绍。