Develop with pleasure!

福岡でCloudとかBlockchainとか。

ソフトフォークを確実にアクティベートするBIP-8

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つの条件のいずれかで行われる。

パラメータ

各ソフトフォークのデプロイの際は、以下のチェーン毎のパラメータを指定する。

  1. name
    ソフトフォークの識別子として使用するのに適切な名称を指定する。単一のBIPをデプロイする場合は、「bipN」という名称を推奨(NはBIP番号)。
  2. bit
    ブロックのnVersionフィールドのどのbitをソフトフォークのロックインやアクティベーションを知らせるのに使用するか指定する。0〜28の範囲内で指定。
  3. startheight
    ソフトフォークのシグナリングを開始するブロック高を指定する。
  4. timeoutheight
    まだLOCKED_INもしくはアクティベートされていない場合に、ソフトフォークをアクティベーションするブロック高を指定する。

パラメータ選択のガイドライン

上記のパラメータを選択する際は、以下のガイドラインを推奨する。

  1. name
    他のソフトフォークと被らない名称を指定する。
  2. bit
    並行でデプロイされているソフトフォークと同じbitを使用しないこと。
  3. startheight
    ソフトフォークを実装したソフトウェアをリリースした日から30日後(もしくは4320ブロック後)のブロック高を指定する。これによりプレリリースしたソフトウェアを使用しているグループによるトリガーを防止する。簡単にするためretarget periodのブロック高にする。
  4. timeoutheight
    startheightの1年もしくは52416ブロック後を指定する。

既にアクティベート済み、もしくはtimeoutheightのブロック高を過ぎていれば、前のソフトフォークで使われたbitを使って新しいソフトフォークをデプロイが可能だが、そういった必要性が出るまで使われていないbitを選択した方がいい。

状態

各ソフトフォークとブロックには、以下の状態が関連付けられる。

  1. DEFINED
    各ソフトフォークが開始した最初の状態
  2. STARTED
    startheightを過ぎたブロック
  3. LOCKED_IN
    STARTEDブロックの最初のretarget periodより後で、ブロックのnVersionフィールドに閾値以上のビットが関連付けられたブロック
  4. ACTIVE
    LOCKED_IN状態のretarget periodが過ぎた後のブロック
  5. 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の値の内、010011を確保している。現状ブロックのnVersionの上位ビットが001でない場合、デプロイの全てのbitは0であると解釈される。

マイナーはLOCKED_INフェーズ中はbitをずっとセットし続ける必要があるので、どういう状況かは確認できる。

新しいコンセンサスルール

各ソフトフォークの新しいコンセンサスルールは、ACTIVE状態の各ブロックで適用される。

状態遷移

https://github.com/bitcoin/bips/raw/fcd34618d7ce594db2a7e9badc4e70f1a2fab6db/bip-0008/states.png

ジェネシスブロックは各ソフトフォークのデプロイについて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のビットマスク

templateversionキーは保持され、サーバーのデプロイメントの設定を示すのに使われる。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はマイナーがソフトフォークに対して何の準備もなくアクティベートされて無効なブロックを作ることがないよう導入された機能であって、ソフトフォーク採用の投票に使うためではないとしている。

bitcoinmagazine.com

現状のSegwitのアクティベートを巡る混乱を思えば、正しく聞こえる。

ただ、BIP-8はタイムアウト期間を過ぎれば無条件にアクティベートされる訳で、実質ネットワークのほとんどが反対するような提案であっても必ずアクティベートできるということになる。もちろんネットワークの大多数が反対しているのであれば、誰もそのソフトフォークを採用しようとせず、そのソフトフォークはアクティベートはされたけど実質使えない状態になる。P2SHのリリース実績をもとにフラグデイでのアクティベートを謳ってはいるけど、P2SHも過半数が賛成しない場合はリリースを延期するとしていたのを考えると、無条件のアクティベートというのも如何なものかと思う。

BIP-9からシフトするか?

BIP-8を使ってSegwitをデプロイしようとBIP-149が定義されているが、現状はBIP-148が主流であるため、BIP-149がデプロイされることはないと思う。 またBitcoin Coreにもまだマージされてはいないので、BIP-9に代わってBIP-8が使われるようになるかどうかは現時点では分からない。