152013年3月11日至12日

比特币拥有两段有效历史记录的那一夜

比特币核心的新旧版本在一个区块上存在分歧。开发者和矿工必须在几小时内决定支持哪个分支——并为此付出了放弃工作的代价。

2013年3月11日晚上,比特币节点开始沿着同一段历史的两个版本继续前进。新版Bitcoin 0.8接受了区块225 430,并在其后继续出块。一部分使用0.7版的旧节点却拒绝了它,因为处理这个区块所需的数据库锁数量超出了Berkeley DB的实际限制。两边都遵循自己的软件,都认为自己的分支有效。新分支起初拥有矿池中大部分的算力,因此一味等待更可能加深分裂,而不是让分裂结束。开发者、交易所和矿池运营商聚到了IRC上。在没有中央指挥中心的情况下,他们必须找到一条让足够多参与者重新汇合的路。

01

一个区块,两种判决

区块225 430并非出于分裂网络的目的而创建。然而,它包含的交易输入比之前的区块多,这样就以一种方式给数据库带来了负担,暴露了不同版本之间的差异。Bitcoin 0.8使用LevelDB,能够处理这个区块。版本0.7使用Berkeley DB,遇到了锁的数量限制,最后出现了错误。

结果非常隐晦。两个节点群组都按照他们的程序能够执行的规则行事。新的分支按照预期的共识规则并不是欺诈性的,而老节点也没有被攻击。网络之所以分裂,是因为一个数据库技术的未文档化的实际限制。

使用0.8版本的矿工在新的区块上继续建造,而使用较旧版本的矿工则继续在其前身的基础上进行。随着两条分支都获得了额外的区块,在一条分支上确认的交易不一定存在于另一条分支上。用户不能安全地依赖通常的确认次数,直到经济上的多数派接受哪一条历史记录为明确。

新分支最初大约承载了六十个百分比的计算能力。在正常情况下,累积工作量更大的链条会获得优势。但是在这里,较旧的节点无法接受新的区块,无论其工作量如何。重新组织本身并不能弥补被本地验证软件认为是无效的区块。

这一事件揭示了“最长链胜出”的简单规则的局限性。更准确地说,胜出的链是节点首先认可为有效的链中拥有最多工作量的链。当节点对有效性产生分歧时,单纯的哈希率不足以立即实现统一。

02

IRC作为临时控制室

开发人员注意到这个问题是当跟踪的节点高度和哈希值开始不同的时候。通信集中在IRC频道上,在那里可以比较日志、分支的性能和大型矿池的反应。Gavin Andresen 后来在BIP 50 中写下了时间线以及那晚做出的决定。Gavin Andresen

没有纯粹正确和明显错误的选择。支持0.8版本将保留工作量更大的链,但会切断大量老节点。回到0.7分支意味着说服新矿工放弃他们已经合法挖出的并且期待奖励的区块。

实际上的返回原因是兼容性问题。0.8版本能够接受为旧分支创建的较小块,而0.7节点无法接受新分支的有问题的块。如果计算能力转移到两个群体都可以接受的分支上,网络就可以在不强制所有旧节点立即升级的情况下重新连接。

这一决定并不是安德烈森能够通过协议下达的命令。开发者联系矿池运营商,向他们解释情况,并建议交易所暂停充值。Bitcoin.org发布了警告,呼吁大型矿池退回旧版软件,让网络重新汇合到兼容的分支上。

这种协调之所以能够快速进行,是因为核心参与者的人数仍然相对较少,人们通过公开渠道彼此相识。这种速度帮助网络避免了更长时间的分裂,同时也暴露了早期运作所依赖的社会联系。比特币没有老板,但有一些人,他们的电话或消息能够转移大量的算力。

IRC 上的对话既没有法律效力,也没有秘密的管理员密钥。它的意义在于,将拥有不同干预能力的人汇聚到了一起。开发者能够解释原因,矿池可以重新分配算力,交易所可以暂停充值,节点运营者可以更换版本。每一步都有各自的风险,没有人能保证其他人会听从。因此,支持兼容 0.7 版本的分支,并非来自网络中心的技术指令,而是一次协调一致的押注——相信这条路径能以最小的用户影响最快地恢复共同历史。这一成功不能脱离当时社区规模较小的背景,也不能被当作通用的危机处理流程。

03

矿池常常牺牲已经完成的工作

关键的一步来自两个矿池:由 Michael Marsee(网名 Eleuthria)运营的 BTCGuild,以及由 Marek Palatinus 运营的 Slush Pool。两位运营者都从0.8版退回0.7版,把算力转向旧分支,从而帮助改变了两条链累计工作量的对比。

这并不是一个免费的姿态。在重新组织时,在废弃分支上挖出的区块不再是主链的一部分,因此他们的奖励不应该保持可花费的状态。BIP 50 明确提到,矿池牺牲了大量的资金,以使网络回到共同的分支上。

一旦较老的分支取得领先地位,更新的节点0.8就可以接受它并重新组织自己的历史记录。旧的节点已经在这条分支上运行了。分裂并没有因为一个版本远程覆盖了另一个版本而结束,而是因为足够多的矿工在旧的分支上创建了更多的工作量,从而使得两个版本都能验证的点成为新的起点。

在这次事件中,至少发生了一次大规模的双重花费,被描述为实验,而不是有计划的攻击。尽管如此,这次事件揭示了真实的风险:交易可能会被确认在后来被放弃的分支上。停止存款的建议并非出于恐慌;它保护了服务免受基于临时历史的账户余额的影响。事件发生在三月,

在那一刻,矿池并不拥有整个网络。它们可以通过矿工委托的算力来影响区块的顺序,但是无法强迫每个全节点接受任意规则。它们的成功依赖于一个狭窄的目标:创建一条既老节点又新节点都认为有效的分支,并为其提供最多的工作量。

04

认真复盘,而非编写胜利神话

Gavin Andresen后来在BIP 50中记录了这次事件。文档没有宣称网络毫无差错,而是解释了起因、经过、被丢弃的区块、双花,以及为更早发现类似分歧而采取的措施。这份公开的分析让危机之夜的经验不再只留在当事人的记忆中,也能供后来的开发者借鉴。

一个教训是,共识不仅仅包括代码注释或文档中写明的规则。广泛使用的程序的实际行为可能会成为网络的限制,即使没有人故意让它具有货币意义。因此,数据库迁移不应被视为纯粹的内部变化,如果它可能会改变对区块的接受。

进一步的教训是需要更谨慎地协调升级,并测试接近运行限制的区块。0.8版本带来了性能改进,但差异只体现在实际链上。事件发生后,开发人员引入了临时限制,并铺平了道路,以便后来能够安全地放弃旧依赖。

这个故事有时被用作证据,证明比特币由少数开发者控制,而有时又被用作证据,证明它是自动自毁的。但是,这两种简化的说法都不准确。人们能够紧急地协调修复,但是他们需要矿池、交易所和节点运营者的合作。软件在每台计算机上单独做出决定,而共同的结果是通过他们的综合作用产生的。

3月11日至12日的那个夜晚,网络最终回到了一条主链上。但分歧并没有自行消失:有人发现了问题,有人暂停服务,矿工则放弃了已经投入的挖矿工作,换回兼容的交易历史。比特币之所以渡过这次危机,既靠规则和参与者承担的经济代价,也靠那些愿意承认这一次新软件必须退让的人。

BIP 50的价值,恰恰在于它没有用一句“去中心化的胜利”为事件收尾。它记录了具体原因、时间线、损失的工作,以及为避免类似意外而规划的措施。一次令人难堪的故障,就这样成为后续测试的依据。但一个问题仍未解决:两个被认为兼容的程序版本之间,究竟藏着多少未曾写明的假设?共识不只是纸上的规则清单。独立的实现和节点必须连在边界情况下也得出相同结果。而这种一致有多重要,往往在它破裂时才显得最清楚。

Atlas

图鉴中的人物与概念

FORK13March 2013 Chain SplitGAVINGavin AndresenSLUSHMarek“Slush”PalatinusPOOLMining PoolNODEFull NodeRULES共识规则REORGReorg
  1. 01
    BIP 50 — 三月 2013 链分叉事后分析

    主要的技术报告关于原因,哈希率,BTCGuild和Slush Pool的协调,双重支出以及修复。

  2. 02
    Bitcoin.org — 11/12 三月 2013 链分叉信息

    当时针对商家和用户在网络分裂期间的公开警告。

  3. 03
    比特币核心 —— 版本0.8.0发布说明

    新的数据库层的背景和版本变化中,意外的不兼容性得到了体现。

下一个故事 · 16Trezor:比特币的钥匙在掌上电脑中