2020/07/04追記 その後BIP-8の内容は更新されたので、内容については以下の記事を参照↓
techmedia-think.hatenablog.com
現在、ソフトフォークのアクティベートと方法はBIP-9のversion bitsを使った方法が採用されている↓
techmedia-think.hatenablog.com
この方法を使ってOP_CSVのソフトフォークがリリースされ(シグナリング開始から約2ヶ月でアクティベート)、Segwitのデプロイも行われている。ただ、BIP-141ベースのSegwitは2016年11月にデプロイが始まったものの、Segwitのシグナリングを発信しているブロックは長らく25%〜30%くらいで、2017年7月11日時点で46%ほどで、アクティベートの閾値である95%にはほど遠い。
このSegwitのアクティベートを促すためにUASFや、Segwit2xなどの提案もされているが、そもそもBIP-9のソフトフォークのアクティベート方法を見直そうという提案がBIP-8になる↓
https://github.com/bitcoin/bips/blob/master/bip-0008.mediawiki
概要
このBIPでは、BIP-9の時間ベースのアクティベートをブロック高ベースに置き換えるとともに、ソフトフォークのアクティベーションを保証する変更について規程している。
動機
BIP-9ではブロックのnVersion
フィールドを再利用して、ソフトフォークの並行デプロイを行うための仕組みが導入された。アクティベーションの際は95%というハッシュレートのほぼ全てのシグナリングが必要になっているが、これは非実用的で、少数のハッシュレートさえあればソフトフォークを拒否することができる。過半数のハッシュレートをベースにしたアクティベーショントリガーであれば、フルノードのアップグレードに代わって過半数のハッシュパワーが新しいルールを適用することでアクティベーションが可能。全てのコンセンサスルールはフルノードによって最終的に強制されるため、最終的に新しいソフトフォークがエコノミーによって強制される。この提案は、これら2つの側面を組み合わせて合理的な期間後にフラグデイによるアクティベーションを提供すると共に、フラグデイ以前でも大部分のハッシュレートによるアクティベーションを有効にする。
ブロック時間についてはいささか信頼性が低く、意図的にもしくは意図せず不正確な時間になる可能性があるため、ブロック時間に基づく閾値は理想的ではない。第二に、BIP-9では指定した時間後の最初のretarget periodに基づいてトリガーがを指定しているが、これは直感的ではない。新しいブロックはそれぞれ1つずつ高さが増えていくので、ブロック高に基づく閾値の方がはるかに信頼性が高く直感的で、難しいretargetを正確に計算することができる。
仕様
概要
この仕様は、MTP(Median Time Past)を利用した時間ベースの閾値をブロック高に置き換え、デプロイのステートマシンにFAILEDが無いという2点を除いてBIP-9と同様である。STARTED
からLOCKED_IN
への状態遷移は以下の2つの条件のいずれかで行われる。
- まだ
LOCKED_IN
になっていない状態で、ブロックのシグナリングの閾値がBIP-9に定義されている閾値に達した場合 STARTED
の状態にあるなかで、ブロックチェーンのブロック高がタイムアウトブロック高に達した場合
パラメータ
各ソフトフォークのデプロイの際は、以下のチェーン毎のパラメータを指定する。
- name
ソフトフォークの識別子として使用するのに適切な名称を指定する。単一のBIPをデプロイする場合は、「bipN」という名称を推奨(NはBIP番号)。 - bit
ブロックのnVersion
フィールドのどのbitをソフトフォークのロックインやアクティベーションを知らせるのに使用するか指定する。0〜28の範囲内で指定。 - startheight
ソフトフォークのシグナリングを開始するブロック高を指定する。 - timeoutheight
まだLOCKED_IN
もしくはアクティベートされていない場合に、ソフトフォークをアクティベーションするブロック高を指定する。
パラメータ選択のガイドライン
上記のパラメータを選択する際は、以下のガイドラインを推奨する。
- name
他のソフトフォークと被らない名称を指定する。 - bit
並行でデプロイされているソフトフォークと同じbitを使用しないこと。 - startheight
ソフトフォークを実装したソフトウェアをリリースした日から30日後(もしくは4320ブロック後)のブロック高を指定する。これによりプレリリースしたソフトウェアを使用しているグループによるトリガーを防止する。簡単にするためretarget periodのブロック高にする。 - timeoutheight
startheight
の1年もしくは52416ブロック後を指定する。
既にアクティベート済み、もしくはtimeoutheight
のブロック高を過ぎていれば、前のソフトフォークで使われたbitを使って新しいソフトフォークをデプロイが可能だが、そういった必要性が出るまで使われていないbitを選択した方がいい。
状態
各ソフトフォークとブロックには、以下の状態が関連付けられる。
- DEFINED
各ソフトフォークが開始した最初の状態 - STARTED
startheight
を過ぎたブロック - LOCKED_IN
STARTEDブロックの最初のretarget periodより後で、ブロックのnVersion
フィールドに閾値以上のビットが関連付けられたブロック - ACTIVE
LOCKED_IN状態のretarget periodが過ぎた後のブロック - FAILED
DEFINED状態で、ブロックチェーンのブロック高がtimeoutheight
より大きくなった状態
Bit flags
ブロックヘッダのnVersion
フィールドは、32 bitのリトルエンディアン整数として解釈され、bitはこの整数内のビット値として選択される。
STARTED状態にあるブロックは、1がセットされているビットのnVersionを取得する。ブロックの上位3ビットは001になる必要があるため、nVersionの値は[0x20000000…0x3FFFFFFF]の範囲内となる。
BIP-34、BIP-66、BIP-65の制約により、使用可能なnVersion
の値は0x7FFFFFFB個のみ。このため独立して並行デプロイできるソフトフォークの数は30個までという制約が付く。上位3 bitを001に制限するため、実際の同時デプロイは29個までとなる。将来この仕組みとは異なる仕組みを使ったアップデートをサポートするのに上位3bitの値の内、010
と011
を確保している。現状ブロックのnVersion
の上位ビットが001
でない場合、デプロイの全てのbitは0であると解釈される。
マイナーはLOCKED_IN
フェーズ中はbitをずっとセットし続ける必要があるので、どういう状況かは確認できる。
新しいコンセンサスルール
各ソフトフォークの新しいコンセンサスルールは、ACTIVE
状態の各ブロックで適用される。
状態遷移
ジェネシスブロックは各ソフトフォークのデプロイについてDEFINED
状態にある。
State GetStateForBlock(block) { if (block.height == 0) { return DEFINED; }
同じretarget period内であれば状態は変化しない。つまり、floor(block1.height / 2016) = floor(block2.height / 2016)
の場合、各デプロイは同じ状態を持つことが保証されている。
if ((block.height % 2016) != 0) { return GetStateForBlock(block.parent); }
それ以外の場合、次の状態が何になるかは前の状態によって決まる。
switch (GetStateForBlock(GetAncestorAtHeight(block, block.height - 2016))) {
ブロック高がstartheight
もしくはtimeoutheight
を迎えるまでは、最初の状態(DEFINED
)のまま。
case DEFINED: if (block.height >= timeoutheight) { return FAILED; } if (block.height >= startheight) { return STARTED; } return DEFINED;
STARTED
状態になった後、タイムアウトを過ぎた場合はLOCKED_IN
に切り替わる。まだタイムアウトになっていない場合はbitをセットする。過去のperiodで充分な数のbitがセットされた場合はLOCKED_IN
に遷移する。この時の閾値は、mainnetが2016ブロック中1916ブロック以上(95%以上)で、testnetが2016ブロック中1512ブロック以上(75%以上)である。
この時、(そうそうないと思われるが)オーバーラップしない形で2つのデプロイに同じbitが使われている可能性もある。この場合、最初のデプロイはLOCKED_IN
に遷移し、同時にその次のデプロイはSTARTED
に遷移する。この直近のperiodでbitをセットしているということは2つのデプロイに対してbitをセットしていることになる。(1つのデプロイはLOCKED_IN
になってるので、次のデプロイが同じタイミングでSTARTED
になる分にはデプロイ自体はオーバーラップしていないという認識)
ブロックの状態は、そのブロック自身のnVersion
には依存せず、その前のperiodのブロックのnVersion
に依存していることに注意すること。
case STARTED: if (block.height >= timeoutheight) return LOCKED_IN;
int count = 0; walk = block; for (i = 0; i < 2016; i++) { walk = walk.parent; if (walk.nVersion & 0xE0000000 == 0x20000000 && (walk.nVersion >> bit) & 1 == 1) { count++; } } if (count >= threshold) { return LOCKED_IN; } return STARTED;
LOCKED_IN
のretarget periodが過ぎたら、自動的にACTIVE
に遷移する。
case LOCKED_IN: return ACTIVE;
ACTIVE
は最終状態なので、このソフトフォークのデプロイはこれで終了になる。
case ACTIVE: return ACTIVE; }
実装上の注意:状態はブロックのチェーンの分岐に沿って維持されるが、ブロックチェーンの再編成が行われた際は再計算が必要な場合がある。
特定のブロックやデプロイの組み合わせの状態は、現状のretarget期間の前にその祖先によって決められることを考慮すると、各multiple-of-2016ブロックの状態の結果をキャッシュすることで、親によってインデックス化され、上記の仕組みを効率的かつ安全に実装することが可能。
警告の仕組み
アップグレードの警告をサポートするため、"implicit bit" mask = (block.nVersion & ~expectedVersion) != 0
を使って、"未知のアップグレード"がトラッキングされる。nVersion
にソフトウェアが予期せぬbitがセットされると、maskは0以外の値になる。"未知のアップグレード"のLOCKED_IN
が検出したら、ソフトウェアはこれから有効になるソフトフォークについて大きく警告する必要がある。さらに次のretarget peroidになって状態がACTIVE
になった場合はさらに大きく警告する必要がある。
getblocktemplateの変更
block templateを要求するオブジェクトは以下の新しい項目を含むよう拡張される。
template request
キー | 必須 | タイプ | 定義 |
---|---|---|---|
rules | no | 文字列の配列 | サポートされているソフトフォークのname のリスト |
templateオブジェクトは以下のように拡張される。
template
キー | 必須 | タイプ | 定義 |
---|---|---|---|
rules | yes | 文字列の配列 | アクティブになっているソフトフォークのname のリスト |
vbavailable | yes | オブジェクト | 保留中のサポートされているソフトフォークのセット。キーがソフトフォークのname で値にそのソフトフォークのbit をセットしたオブジェクト。 |
vbrequired | no | 数値 | ソフトフォークのversion bitのビットマスク |
template
のversion
キーは保持され、サーバーのデプロイメントの設定を示すのに使われる。versionbitsが使われている場合、version
はversionbitsの範囲内([0x20000000...0x3FFFFFFF])でなければならない。マイナーは、テンプレートのvbavailable
にリストされており、vbrequired
のbitに含まれていないソフトフォークのbitについては、特別なmutable
キー無しにブロックバージョンのbitをセットしたりクリアしたりすることができる。
ソフトフォークのデプロイメント名は、rules
内にリストされているかvbavailable
のキーに含まれており、プレフィックスとして!を付けることができる。このプレフィックスが無いと、GBTクライアントはそのルールがテンプレートに影響を与えることはないと判定してもいい。この典型的な例はBIPの16, 65, 66, 68, 112, や 113で以前のトランザクションが有効でなくなった時である。クライアントがプレフィックスの無いルールを理解していない場合、クライアントはマイニングのため変更されていないものを使用する可能性がある。一方、このプレフィックスを使用するとブロックの構造や生成トランザクションに微妙な変更があることを示す。この例はBIP-34(コミットメント構造の変更)やBIP-141(txidのハッシュの変更とコインベーストランザクションへのコミットメントの追加)。プレフィックス!のルールを理解していないクライアントは、テンプレートを処理しようとしてはならず、変更されていないくてもマイニングに使用しないこと。
参照実装
Comparing bitcoin:master...shaolinfry:bip8-height · bitcoin/bitcoin · GitHub
後方互換性
BIP-8とBIP-9のデプロイメントでは、同時デプロイメントの際にbitを共有しないこと。BIP-9のみを実装しているノードは、タイムアウトまでにハッシュパワーが閾値に達しない場合BIP-8のソフトフォークをアクティベートしないが、アクティベートされたノードによって作られたブロックは引き続き受け入れる。
デプロイメント
BIP-8を使ったデプロイメントの対象は以下で確認できる。
https://github.com/bitcoin/bips/blob/master/bip-0008/assignments.mediawiki
所感
ソフトフォークのアクティベートの歴史
BIP-9以前は、ブロックのnVersion
のバージョンをインクリメントしてデプロイする方法でソフトフォークのデプロイが行われてきた。
techmedia-think.hatenablog.com
それより前はP2SHのように、ソフトフォークを適用する日(フラグデイ)を決めてアクティベートする方法も取られていた。P2SHのアクティベートは、フラグデイ(2012年2月1日)から過去7日間のブロックを対象に、ブロックのコインベースに/P2SH/
という文字列が含まれているブロックが550個以上あれば、2月15日以降のタイムスタンプを持つブロックは全てP2SHの検証が行われるようになるというものだった。1週間に作られるブロックが約1000個なので、550という数字はネットワークの過半数と言える。もちろん逆に大多数のハッシュパワーがP2SHをサポートしない場合は、P2SHのリリースは延期されることになっていた。
P2SHを見るとソフトフォークのアクティベートの閾値は初期は過半数であったのが、BIP-34やBIP-9などによって、75%、最終的にハッシュパワーの95%というほぼ全てにまで上がるようになった。ネットワークを安定に保つという視点であれば、95%という閾値も本来あるべき閾値であると思う。ただ、同時にソフトフォークを採用するかどうかの投票の側面が強くなってきたとも言える。
ソフトフォークの採用は誰が決める?
以下の記事では、コア開発者の1人であるEric Lombrozoが、そもそもBIP-9はマイナーがソフトフォークに対して何の準備もなくアクティベートされて無効なブロックを作ることがないよう導入された機能であって、ソフトフォーク採用の投票に使うためではないとしている。
現状のSegwitのアクティベートを巡る混乱を思えば、正しく聞こえる。
ただ、BIP-8はタイムアウト期間を過ぎれば無条件にアクティベートされる訳で、実質ネットワークのほとんどが反対するような提案であっても必ずアクティベートできるということになる。もちろんネットワークの大多数が反対しているのであれば、誰もそのソフトフォークを採用しようとせず、そのソフトフォークはアクティベートはされたけど実質使えない状態になる。P2SHのリリース実績をもとにフラグデイでのアクティベートを謳ってはいるけど、P2SHも過半数が賛成しない場合はリリースを延期するとしていたのを考えると、無条件のアクティベートというのも如何なものかと思う。
BIP-9からシフトするか?
BIP-8を使ってSegwitをデプロイしようとBIP-149が定義されているが、現状はBIP-148が主流であるため、BIP-149がデプロイされることはないと思う。 またBitcoin Coreにもまだマージされてはいないので、BIP-9に代わってBIP-8が使われるようになるかどうかは現時点では分からない。