一个消息要传遍全网,需要多久?

这个数字后面会反复出现——它决定孤块率、决定区块能有多大、决定共识协议的安全假设成不成立。但它不是网卡参数,也不是"网速"。

打个比方

它更像一个消息在人群里传开需要多久

这取决于两件事:每个人告诉几个人(每一跳的扇出),以及要传几轮才能覆盖所有人(跳数)。人越多,轮数按对数增长——不算太糟。而真正的代价藏在第一件事上:你告诉的人越多,传得越快,但你自己要说的话也越多(带宽)。

这一讲要拆的就是这个权衡,以及它如何决定了后面几讲的所有上限。

一、一个被反复使用却从未推导的量

后面几讲会不断用到一个数:

第 16 讲:孤块率 ≈ 传播时间 / 出块间隔
第 26 讲:扩容第一个瓶颈 —— 区块变大 ⟹ 传播变慢 ⟹ 孤块率↑
第 29 讲:数据可用性采样,采样请求也要在网络上跑

但"传播时间"本身从哪来? 它不是网卡参数,也不是"网速"——它是网络层协议设计的产物。这一讲把它拆开。

顺序上它必须在共识之前:第 2 讲说过,共识协议的安全性证明都建立在某种网络假设之上(同步 / 部分同步 / 异步)。那个假设成不成立,由这一层决定。

⚠️ 这一讲读起来会像"工程细节",容易被当成可以跳过的一节。恰恰相反——上面那句话的意思是:前面所有漂亮的安全性证明,全都挂在这一层给出的那个数字上。 如果传播时间比协议假设的大一个数量级,那些证明就不再适用,而没有任何人会收到报错。

二、为什么必须是 P2P

第 1 讲的出发点是"没有可信第三方"。这个要求对网络层同样成立——如果所有交易都要经过某台服务器才能传开,那台服务器就能审查交易,前面所有的密码学都白做了。

于是网络层也必须是无许可的对等网络,代价是三条:

① 没有全局视图      —— 没有任何节点知道网络里有多少节点、都是谁
② 消息不可靠        —— 可能丢失、重复、乱序到达
③ 邻居可能撒谎      —— 你连上的节点可能是攻击者

⚠️ 注意网络层的安全目标和你的直觉可能不同:

不是【保密】—— 交易和区块本来就是公开数据,加密没有意义。
是【可达性】—— 让每一个诚实节点,最终都能收到每一条消息。

⭐ 换句话说,这一层要防的不是窃听,是"隔离"——第七节会说明,隔离一个节点足以让它被双花。

三、gossip:为什么是泛洪而不是广播

没有全局视图,就不可能"广播给所有人"。实际做法是泛洪(flooding)

① 每个节点维持少量邻居连接
     比特币:默认 8 个出站 + 至多 117 个入站
     以太坊 devp2p:默认约 13 个 peer
② 收到一条没见过的消息 ⟹ 验证它 ⟹ 转发给除来源外的所有邻居
③ 已经见过的消息直接丢弃(靠哈希去重)

消息像水波一样一圈一圈扩散。 覆盖全网需要的跳数,就是这个随机图的直径:

每个节点 d 个邻居,n 个节点:
      第 0 跳    1 个节点知道
      第 1 跳    d 个
      第 2 跳    d² 个
      ...
      第 k 跳    dᵏ 个

  ⟹ 覆盖全网需要 k ≈ log_d(n) 跳

代入真实数字:一万个节点、每个 8 个邻居,只要约 4.4 跳。 这就是 gossip 有效的原因——跳数随节点数对数增长,网络翻十倍,跳数只多一点点。

⚠️ 但第 ② 步里有一个容易被忽略的动作:先验证,再转发。

如果转发不验证:
   攻击者发一个垃圾区块 ⟹ 全网帮他免费传播
   ⟹ ⭐ 这是一个放大 n 倍的 DoS

所以每一跳都必须验证。
⟹ 而验证是要花时间的,这段时间【进入了传播时间】。

记住这个结构:安全(必须验证)和速度(验证费时)在这一层是直接冲突的。

四、传播时间的分解

⭐ 这一节把"消息在人群里传开"拆成两项:传几轮(跳数)和每轮花多久(每跳的时延与带宽)。 前面那个权衡——告诉的人越多传得越快、但自己要说的话也越多——在这里变成了具体的数。

传播时间 ≈ 跳数 × ( 物理延迟 + 验证时间 + 传输时间 )
                      ↑           ↑           ↑
                    光速下限    重执行交易   区块大小/带宽

三项的性质完全不同:

分量 决定于 能不能靠加带宽改善
物理延迟 光速与地理距离 不能——这是硬下限
验证时间 区块里的交易量、状态访问 不能(是 CPU 和磁盘)
传输时间 区块字节数 ÷ 带宽

物理延迟的下限值得算一下:

光在光纤中约 200,000 km/s(比真空慢 1/3)
地球半周长约 20,000 km

⟹ ⭐ 地球对跖点之间的单向延迟下限 ≈ 100 ms
   实际跨洲 RTT 通常 150–250 ms(要绕路、要过路由器)

⚠️ 这一项是任何工程都消不掉的。 这正是第 26 讲那句"即使带宽足够,全球节点之间的物理延迟不会变"的出处——“多买带宽就能调大区块"之所以错,错在它只看见了三项里的一项。

一个可以算的模型

// 一次 gossip 传播的耗时模型。
// 这不是精确仿真,是数量级估算——但它足以说明每一项各自的权重。
package p2p

import "math"

type Params struct {
	Nodes      int     // 全网节点数
	Peers      int     // 每个节点的邻居数
	HopLatency float64 // 每跳物理延迟(秒)
	VerifyTime float64 // 每跳验证耗时(秒)
	BlockBytes float64 // 每跳要传的字节数
	Bandwidth  float64 // 带宽(字节/秒)
}

// Hops 返回覆盖全网的期望跳数:随机图直径约 log_d(n)。
func Hops(nodes, peers int) float64 {
	if nodes <= 1 || peers <= 1 {
		return 0
	}
	return math.Log(float64(nodes)) / math.Log(float64(peers))
}

// Propagation 返回一个区块传遍全网的估算时间。
func Propagation(p Params) float64 {
	perHop := p.HopLatency + p.VerifyTime + p.BlockBytes/p.Bandwidth
	return Hops(p.Nodes, p.Peers) * perHop
}

// OrphanRate 就是第 16 讲那条公式。
// 它成立的前提是:出块是泊松过程,且传播期间挖出的块会成为孤块。
func OrphanRate(propagation, blockInterval float64) float64 {
	return propagation / blockInterval
}

代入一万个节点、8 邻居、每跳 50 ms、验证 10 ms、100 Mbps:

                        1 MB 完整区块     15 KB 紧凑区块
────────────────────────────────────────────────────────
跳数                       4.43              4.43
物理延迟/跳                50 ms             50 ms
验证/跳                    10 ms             10 ms
传输/跳                    80 ms          ⭐  1.2 ms
────────────────────────────────────────────────────────
每跳合计                  140 ms             61 ms
传播时间                  620 ms            271 ms
────────────────────────────────────────────────────────
孤块率 @ 600 秒出块       0.10%             0.045%
孤块率 @ 12 秒出块     ⚠️ 5.2%              2.3%

看最后两行:同一个网络,出块间隔从 600 秒改成 12 秒,孤块率涨了五十倍。出块间隔和网络层不是两个独立的旋钮。

五、优化:把传输时间从关键路径上拿掉

上表已经泄露了答案——传输时间是三项里唯一能大幅压缩的,办法不是加带宽,是少传字节。

Compact Blocks(BIP-152)

关键观察:一个区块里的交易,你的邻居大概率早就有了——它们在几秒到几分钟前就通过交易池 gossip 传过来了。既然如此,出块时没必要再传一遍

① 只发:区块头 + 每笔交易的 6 字节短 ID + coinbase 交易
   ⟹ 一个 2500 笔交易的区块,从 1 MB 压到约 15 KB

② 对方用短 ID 在自己的交易池里查找、重建区块

③ ⚠️ 如果缺了几笔,再发一轮 getblocktxn 要回来
   ⟹ 代价是多一个 RTT

这是一次典型的"用带宽换延迟"的反向操作:数据被提前分散传输了(交易 gossip),出块时只传增量

高带宽模式更进一步:不等对方请求,直接把紧凑区块推过去,省掉一次询问的往返。

中继网络

再往上是专用的低延迟骨干(比特币的 FIBRE、以太坊的各类 relay):预先建立的连接、前向纠错(丢包不用重传)、地理上分布的节点。

⚠️ 它的代价要看清楚:这是一层中心化的基础设施。矿工/验证者依赖少数几个中继商,等于在网络层引入了新的信任点——第 34 讲的 PBS 会把这个问题放大成一个结构性议题。

六、交易池的传播与 Erlay

区块之外,交易也要 gossip,而且冗余问题更严重:

朴素做法:收到一笔交易,转发给全部 8 个邻居
⟹ 每个节点会从多个邻居各收到同一笔交易一次
⟹ ⭐ 带宽消耗 ∝ 邻居数,而不是 ∝ 交易数

第一层缓解是 INV / GETDATA 两阶段:先只发 32 字节的哈希(inventory),对方发现自己没有才来要正文。省掉了重复的正文,但哈希本身仍然是按邻居数重复的

Erlay(BIP-330) 换了个思路:与其泛洪,不如让两个节点协调各自交易池的差集

用 minisketch(一种集合协调编码):
   两个各有几千笔交易的节点,
   只要交换 O(差集大小) 的数据,就能算出彼此缺什么。

⟹ 带宽从 O(邻居数) 降到近似 O(1)
⟹ 代价:延迟略增(要等一个协调周期)

为什么这件事重要,值得说明白:带宽是"能不能跑一个节点"的门槛之一。降低带宽 = 更多普通人能跑全节点 = 第 3 讲意义上的去中心化。 这是一个网络层优化直接兑换成安全性的例子。

七、网络层的攻击面

Eclipse 攻击

目标不是攻破密码学,是把一个节点从真实网络里"摘出去”:

① 攻击者用大量 IP 占满受害节点的全部连接槽
② 受害者从此只能看见攻击者构造的"世界"
③ ⟹ 攻击者可以:
      · 隐瞒真实的最长链 ⟹ 受害者以为一笔交易确认了(双花)
      · 隐瞒新区块 ⟹ 受害者的算力全部浪费在旧高度上
      · 把受害者的算力"借"给自私挖矿(第 16 讲)

这个攻击的深刻之处在于它打的位置:

所有共识安全性证明,都有一条前提叫
       "诚实节点之间能够互相通信"。

⚠️ Eclipse 攻击不攻击共识算法 —— 它攻击这条前提。
⟹ 算法一行没错,结论照样不成立。

防御全部是网络层的工程手段:出站连接优先信任(出站是自己选的,入站是别人找上门的)、地址表按 /16 网段分桶(同一网段的大量 IP 不能挤占同一个桶)、锚点连接(重启后回连上次的稳定 peer)、以及在高价值场景下手工指定 peer

Sybil:网络层没有身份

⚠️ 这里有一个非常常见的误解,必须点破:

PoW / PoS 解决的是【出块权】的 Sybil 问题
   —— 你不能靠多开节点获得更多出块机会。

它们【完全没有】解决【连接槽】的 Sybil 问题
   —— 在网络层,开一万个节点仍然是免费的。

所以 Eclipse 是 PoW 链和 PoS 链共有的问题,与共识机制无关。

网络分区与路由层攻击

拥有路由能力的对手(ISP、能发 BGP 通告的机构)可以劫持流量或切断区域连接。已有研究记录过针对比特币流量的 BGP 劫持事件。分区期间,两边各自认为自己在正常出块——这正是第 2 讲 CAP 与部分同步假设讨论的现实版本。

来源去匿名

⚠️ 一笔交易第一个广播它的节点,大概率就是发起者。 观察者只要连上足够多的节点、记录每笔交易的首次出现位置,就能把交易和 IP 关联起来——这在链上数据里是完全看不见的。Dandelion++ 之类的方案先让交易沿一条随机路径"茎"传播若干跳,再进入泛洪的"花"阶段,用来打断这个关联。第 33 讲会把它放回隐私的整体图景里。

八、网络假设与共识的联动

现在可以回答第 2 讲留下的问题了:出块间隔是怎么定出来的?

⭐ 出块间隔 = 传播时间 × 安全系数

安全系数越大,孤块率越低、越安全,但确认越慢。
出块间隔 传播时间量级 裕度 代价
比特币 600 秒 ~1 秒 约 600× 确认极慢
以太坊 12 秒 ~1–2 秒 约 10× ⚠️ 需要更精细的分叉选择(第 20 讲)
高速链 亚秒级 同量级 接近 1 需要小而地理集中的验证者集合

这张表解释了一件常被误读的事:一条链"出块快"不是工程上更强,是在网络裕度上取了更激进的位置,并用其它地方的代价(验证者门槛、更复杂的分叉选择、更高的重组概率)付了账。

⚠️ 裕度接近 1 时会发生什么:传播时间的波动(某次跨洲拥塞)直接变成分叉,链要靠更复杂的规则来收敛。这不是"更好的技术",是同一条权衡曲线上的另一个点——和第 3 讲对"不可能三角"的处理是同一个道理。

九、本讲小结

  • “传播时间"不是网速,是网络层协议设计的产物——它被第 16 讲的孤块率公式、第 26 讲的扩容瓶颈、第 29 讲的采样反复当常量用,这一讲把它推导出来。
  • 网络层也必须无许可,否则中心服务器就能审查交易,前面所有密码学都白做。代价是没有全局视图、消息不可靠、邻居可能撒谎。
  • ⚠️ 网络层的安全目标是可达性而不是保密——数据本来公开,要防的是"隔离"而不是"窃听”。
  • gossip 的跳数是 log_d(n):一万节点、8 邻居只要约 4.4 跳。跳数随节点数对数增长,这是泛洪可行的根本原因。
  • 每一跳必须先验证再转发,否则就是一个放大 n 倍的 DoS。于是验证时间进入了传播时间——安全和速度在这一层直接冲突。
  • ⭐⭐ 传播时间 ≈ 跳数 × (物理延迟 + 验证时间 + 传输时间),而三项里只有传输时间能靠加带宽改善。 光速给出的地球对跖点单向延迟下限约 100 ms,任何工程都消不掉——这才是"带宽够了就能调大区块"错在哪里。
  • 同一个网络,出块间隔 600 秒 → 12 秒,孤块率涨约 50 倍。 出块间隔和网络层不是独立的旋钮。
  • Compact Blocks 把 1 MB 压到约 15 KB,靠的不是压缩算法,是交易早就通过交易池传过去了,出块时只传增量——代价是缺交易时多一个 RTT。
  • ⚠️ 中继网络是一层中心化基础设施:它降低了延迟,同时在网络层引入了新的信任点(第 34 讲 PBS 会放大这个问题)。
  • Erlay 用集合协调代替泛洪,把交易传播带宽从 O(邻居数) 降到近似 O(1)。⭐ 带宽是跑节点的门槛,降带宽直接兑换成第 3 讲意义上的去中心化。
  • ⭐⭐ Eclipse 攻击不攻击共识算法,它攻击共识证明的前提——“诚实节点之间能互相通信”。 前提不成立时,算法一行没错,结论照样不成立。
  • ⚠️ PoW/PoS 解决的是出块权的 Sybil,完全没有解决连接槽的 Sybil——在网络层开一万个节点仍然免费。所以 Eclipse 是两类链共有的问题。
  • 首个广播交易的节点大概率就是发起者,这个关联在链上数据里完全看不见(第 33 讲)。
  • 出块间隔 = 传播时间 × 安全系数:比特币约 600×,以太坊约 10×,高速链接近 1×。“出块快"不是技术更强,是在网络裕度上取了更激进的位置,并在别处付账。

思考题

  1. 为什么网络层不能用"一台服务器广播给所有人”?请从第 1 讲的出发点推导,而不是直接说"不去中心化"。
  2. 每个节点 12 个邻居、全网 5 万个节点,覆盖全网要几跳?如果节点数涨到 50 万呢?说明这个增长的性质。
  3. 如果 gossip 转发时验证,攻击者能造成多大的放大?给出一个量化的估计。
  4. 传播时间的三个分量里,为什么只有一项能靠加带宽改善?请分别说明另外两项受什么限制。
  5. 用本讲的模型算:把区块从 1 MB 加到 8 MB(其余不变),传播时间和 12 秒出块下的孤块率各变成多少?
  6. Compact Blocks 把 1 MB 压到 15 KB,它压缩的到底是什么?在什么情况下它反而会变慢
  7. 中继网络降低了延迟,它引入了什么新的信任假设?如果所有大矿工都用同一个中继商,会发生什么?
  8. Erlay 用带宽换了延迟。为什么这笔交换在"去中心化"这个目标下是划算的?
  9. 一条 PoS 链的开发者说"我们用 PoS,所以不怕 Sybil 攻击"。这句话在什么意义上对、什么意义上错?
  10. 为什么说 Eclipse 攻击"打的是共识证明的前提"?请写出中本聪共识安全性论证里被它推翻的那一条假设,并说明推翻之后结论为什么就不成立了。