BIP: 50
Title: March 2013 Chain Fork Post-Mortem
Authors: Gavin Andresen <gavinandresen@gmail.com>
Status: Deployed
Type: Informational
Assigned: 2013-03-20
License: PD
何がまずかったか
トランザクションの入力の総数が、それまでに見られたどのブロックより多いブロックが採掘され、ネットワークに流された。ビットコイン0.8のノードはこれを処理できたが、0.8より前の一部のノードはこれを退け、ブロックチェーンに予期しない分岐が起きた。0.8より前と互換性のないチェーン(以下、0.8のチェーン)には、その時点でマイニングのハッシュパワーの約60%があったため、分岐は自然には解消しなかった(0.8より前のチェーンが総作業量で0.8のチェーンを上回っていれば、0.8のノードは0.8より前のチェーンへ再編成を強いられ、分岐は自然に解消していたはずだった)。
できるだけ早く正規のチェーンを取り戻すため、BTCGuildとSlushは、プールも大きなブロックを退けるよう、ビットコイン0.8のノードを0.7へ戻した。これでハッシュパワーの過半数が大きなブロックを含まないチェーンに移り、最終的に0.8のノードは0.8より前のチェーンへ再編成した。
この間に、少なくとも一件の大きな二重支払い (https://bitcointalk.org/index.php?topic=152348.0)があった。ただし、それは可能かどうかを試した人によるもので、悪意によるものではなかった。
何がうまくいったか
- 分岐はすぐに検知された。
- 必要な人がオンラインでIRCにいたか、直接連絡がついた。
- マレク・パラティヌス(Slush)とマイケル・マーシー(BTCGuildのEleuthria)は、かなりの額の資金を犠牲にすることになったにもかかわらず、ノードをすばやく戻して、0.8より前のチェーンを正規のチェーンとして回復させた。
- 主要な取引所への入金とBitPayでの支払いも、すぐに停止され(そして再開され)た。
- 幸い、加盟店への攻撃は一件だけで、それも実際に資金を盗むつもりのない人によるものだった。
根本原因
0.8より前のビットコインは、大きいが他の点では有効なブロックを処理するのに足りるだけのBerkeley DBのロックを設定していない。Berkeley DBのロックは、APIを使う側が想定する負荷に応じて手で設定しなければならない。マニュアルにはこうある。
ロック、ロッカー、ロックオブジェクトの最大数を選ぶ推奨の方法は、アプリケーションを負荷の高い条件で動かし、ロックシステムの統計を見て、使われたロック、ロッカー、ロックオブジェクトの最大数を確かめることである。そして、安全のためにその値を2倍にする。
BDBのロックの設定が足りなかったことで、それがブロックの有効性を決めるネットワークの合意のルールに、暗黙のうちになっていた(ただし、ロックの使用量はノードごとに異なりうるので、一貫しない、安全でないルールだった)。
最大の大きさのブロックはテストネットで問題なく処理されていたため、もっと小さくても、使えるロックより多くのロックを必要とするブロックがありうることに、誰も思い至らなかった。0.7より前は、手を加えていないマイニングのノードがブロックの最大の大きさを自分で500,000バイトに制限しており、それもこのケースが起きるのを防いでいた。0.7でこの目標の大きさが設定できるようになり、事故の前の週に、マイナーはこの目標を上げるよう勧められていた。
ビットコイン0.8はBerkeley DBを使っていなかった。代わりにLevelDBに移っており、LevelDBにはBDBと同じロックの制限がなかった。そのため、分岐を起こしたブロックを問題なく処理できた。
BDBのロックは、再編成を処理するあいだにも必要になることに注意されたい。0.8より前の版は、有効な再編成の一部を処理できないことがある。
これは、ネットワーク全体が0.7.2で動いていたとしても問題になる。0.7.2のノードの一台が、ほかのノードが検証できないブロックを作ることも、0.7.2のノードが、相手のノードが検証できないブロックの再編成を作ることも、理論上はありうる。各ノードのblkindex.datデータベースの中身は同じではなく、必要なロックの数はディスク上のblkindex.datの正確な配置に左右されるからである(ロックはページごとに取られる)。
対応項目
すぐに
済み: 0.8.0から直接分けた0.8.1をリリースし、今後2か月間、次の新しいルールを持たせる。
- 10,000を超えるロックを取らせそうなブロックを退ける。
- 作るブロックの最大の大きさを500,000バイトに制限する
- 古い版向けに、同じルールを実装し、そのうえでロックの最大数を537,000に増やすパッチをリリースする
- bitcoin.orgにウェブページを作り、利用者に0.8.1へのアップグレードを強く勧めるとともに、どうしてもできない場合にDB_CONFIGでロック数を537,000にする方法を案内する。
- 今後2か月にわたり、古い版の利用者に、そのウェブページを示すアラートを繰り返し送る。
アラートシステム
済み: アラートシステムの鍵に誰がアクセスできるかを見直し、全員が互いの連絡先を持っていることを確かめ、鍵にアクセスできる人で時間帯をうまく重ねる。
済み: サービスがアラートを時を置かず受け取れるよう、bitcoindに新しい機能を実装する。-alertnotify=<command> AppliesToMe()のアラートを受け取ったときにコマンドを実行する。
済み: テスト用のアラートを52件あらかじめ作っておき、毎週決まった時刻に-testnetで流す(-alertnotifyのスクリプトを、できるだけ実際に近い条件で試せるように)。
マイケル・グロナガーの案: 加盟店や取引所(そしておそらくプールも)に、新しいコードを、ネットワークの多数派の版を動かすbitcoindの後ろで動かすよう勧める。
セーフモード
済み: メインのチェーンでなくても、十分に長いサイドチェーンが見つかったらアラートを出すことも考えられる。プールはこれを使って、長いサイドチェーンが突然どこからともなく現れたときに、支払いを自動で止められる(攻撃者がそのようなものを採掘するのは難しい)。
テスト
テストネットでボットを動かし始める。ボットはテストネットの蛇口からコインを受け取り、それを分けたりまとめたりするランダムなトランザクションを大量に作り、最後に蛇口へ返す。テストネットでランダムなオンラインのテストをしていれば、この病的な型のブロックをもっと早く見つけられたかもしれない。
二重支払い
チェーンの両側がトランザクションを同じ順で受け取っていたにもかかわらず、二重支払いの攻撃が成功した。原因は、マイニングプールのノードを戻したときにメモリープールが消えたことだと考えられる。解決策は、ノードが起動時にメモリープールを互いに同期することだが、それには、メモリープールの期限切れの方針を実装する必要がある。今は、未承認のトランザクションをシステムから取り除く手段が、ノードの再起動しかないからである。
決着
2013年8月16日、ブロック252,451(0x0000000000000024b58eeb1134432f00497a6a860412996e7a260f47126eed07)がメインのネットワークに受け入れられ、パッチを当てていないノードはネットワークから分かれた。
著作権
本文書はパブリックドメインに置かれる。

