ビットコイン・インスティテュート

BIP 8 — 高さで​ロックインする​バージョンビット

Shaolin Fry 2017年2月1日
  BIP: 8
  Title: Version bits with lock-in by height
  Authors: Shaolin Fry <shaolinfry@protonmail.ch>
           Luke Dashjr <luke+bip@dashjr.org>
  Status: Complete
  Type: Informational
  Assigned: 2017-02-01
  License: BSD-3-Clause OR CC0-1.0
  Version: 1.0.0

概要

本書は、いくつか認識された誤りを是正したBIP9の代替案を規定する。 開始とタイムアウトにはPOSIXタイムスタンプではなくブロック高を用いる。 さらに、後方互換の変更(以下「ソフトフォーク」と呼ぶ)の起動を保証できる起動パラメーターを導入する。

本書における “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”、“OPTIONAL” は、RFC 2119に記載のとおりに解釈される。

動機

BIP9は、ブロックのnVersionフィールドを転用することで、並行するソフトフォーク展開を行う仕組みを導入した。起動はほぼ全会一致に近いハッシュレートのシグナリングに依存しており、これは現実的でない場合があり、シグナリングを行わない少数派のハッシュレートによる拒否権行使を招くおそれがある。ハッシュレートの超多数決に基づく起動トリガーは、フルノードのアップグレードを待たずに多数派のハッシュパワーが新ルールを強制することで、加速した起動を可能にする。すべてのコンセンサスルールは最終的にフルノードによって強制されるため、いずれ新しいソフトフォークは経済圏によって強制されることになる。本提案はこの二つの側面を組み合わせ、妥当な期間の経過後に特定期日での任意の一斉起動を提供するとともに、その期日より前でもハッシュレートの多数派による加速起動を可能にする。

タイムスタンプをブロック高の代わりに用いることについては、ハッシュレートの急激な喪失が遅い時期の起動を妨げるリスクがあることが判明していた。

ブロック時刻はいくぶん信頼性に欠け、意図的または非意図的に不正確になりうるため、ブロック時刻を基準とするしきい値は望ましくない。次に、BIP9は与えられた時刻以降で最初のリターゲットに基づくトリガーを規定していたが、これは直感的でない。新しいブロックごとに高さが必ず1ずつ増加するため、ブロック高を基準とするしきい値ははるかに信頼性が高く直感的であり、難易度リターゲットに対して正確に算出できる。

仕様

パラメーター

各ソフトフォーク展開は、以下のチェーンごとのパラメーターによって規定される(詳細は後述)。

  1. nameは、識別子として使用するのに適した、ソフトフォークの簡潔な説明を示す。
  2. bitは、ソフトフォークのロックインおよび起動をシグナリングするために用いる、ブロックのnVersionフィールド中のビット位置を定める。{0,1,2,…,28} の集合から選ばれる。
  3. startheightは、そのビットが意味を持ち始める最初のブロック高を定める。
  4. timeoutheightは、マイナーによるシグナリングが終了するブロック高を定める。この高さに達した時点で、ソフトフォークが(当該ブロック自身のビット状態を除いて)まだロックインしていなければ、その展開はそのブロックのすべての子孫において失敗したものとみなされる。
  5. thresholdは、リターゲット期間ごとに、後続期間でのソフトフォークのロックインを示すために必要な最小ブロック数を定める。
  6. minimum_activation_heightは、ソフトフォークが有効化されることを許される最初のブロック高を定める。
  7. lockinontimeoutは真偽値であり、trueに設定された場合、最終期間中のブロックはシグナリングが必須となり、ソフトフォークがtimeoutheightまでにロックインすることが保証される。

選択の指針

以下は、ソフトフォークについてこれらのパラメーターを選択する際の指針である。

  1. nameは、同時に存在するか否かを問わず、いかなる2つのソフトフォークも同じ名前を使わないように選ぶべきである。単一のBIPで記述される展開については、Nを該当するBIP番号として “bipN” という名前を用いることが推奨される。
  2. bitは、同時に存在する2つのソフトフォークが同じビットを使わないように選ぶべきである。選んだビットは、他の目的で(正当であるか否かを問わず)実際に使用中のものと重複すべきではない。
  3. startheightは将来のあるブロック高に設定すべきである。minimum_activation_heightを設定しない場合、startheightは経済活動の大部分が起動パラメーターを含むソフトウェアへアップグレードを終えていると見込まれる高さに設定すべきである。潜在的なリリース遅延も考慮しておくべきである。minimum_activation_heightを設定する場合、startheightはパラメーターを含むソフトウェアがリリースされると見込まれる直後に設定してよい。これにより、アップグレードのための時間がシグナリング開始前ではなくLOCKED_IN状態の期間へと移る。
  4. timeoutheightは、経済圏全体がアップグレードを終えていると見なすのが妥当な時期のブロック高、おそらくstartheightから少なくとも1年、あるいは52416ブロック(26リターゲット間隔)後に設定すべきである。
  5. thresholdは1815ブロック(2016の90%)、テストネットでは1512(75%)とすべきである。
  6. minimum_activation_heightは、startheightがパラメーターを含むソフトウェアのリリース予定直後に設定される場合、数リターゲット期間分先に設定すべきである。minimum_activation_heightは、経済活動の大部分が起動パラメーターを含むソフトウェアへアップグレードを終えていると見込まれる高さに設定すべきである。これにより、ノードがアップグレードできるようLOCKED_IN状態により多くの時間を費やせるようになる。0に設定して、LOCKED_IN状態を単一のリターゲット期間とすることもできる。
  7. lockinontimeoutは、無視できない割合のマイナーからの政治的な反対が見込まれる、または実際に見られるソフトフォークについてはtrueに設定すべきである(初回の展開後に設定することもできるが、一度設定すると解除はできない)。

同じビットを用いる後続の展開は、startheightが前回のtimeoutheightまたは起動よりも後であれば可能だが、必要になるまでは推奨されず、必要になった場合でも、不具合のあるソフトウェアを検出するための間隔を設けることが推奨される。

startheight、timeoutheight、minimum_activation_heightは2016の正確な倍数(すなわちリターゲット境界)でなければならず、timeoutheightはstartheightから少なくとも4032ブロック(2リターゲット間隔)後でなければならない。

状態

各ブロックとソフトフォークには、展開状態が関連付けられる。取りうる状態は以下のとおりである。

  1. DEFINEDは、各ソフトフォークが開始時に取る最初の状態である。ジェネシスブロックは、定義上、各展開についてこの状態にある。
  2. STARTEDは、startheight以降のブロックに対する状態である。
  3. MUST_SIGNALは、LOCKED_INに達しておらずlockinontimeoutがtrueである場合の、タイムアウトの1リターゲット期間前に対する状態である。
  4. LOCKED_INは、STARTED(またはMUST_SIGNAL)ブロックを含む最初のリターゲット期間のうち、対象ビットがnVersionに設定されたブロック数がthreshold以上であった期間の後、少なくとも1リターゲット期間にわたって続く状態である。ソフトフォークは、少なくともminimum_activation_heightに達するまでLOCKED_INにとどまる。
  5. ACTIVEは、LOCKED_IN状態の後のすべてのブロックに対する状態である。
  6. FAILEDは、LOCKED_INに達しなかった場合の、timeoutheight以降のすべてのブロックに対する状態である。

ビットフラグ

nVersionブロックヘッダーフィールドは(現状のとおり)32ビットのリトルエンディアン整数として解釈され、ビットはこの整数中の値 (1 << N)(Nはビット番号)として選択される。

STARTED状態にあるブロックは、対象のビット位置が1に設定されたnVersionを持つ。そうしたブロックの上位3ビットは001でなければならず、したがって実際に取りうるnVersion値の範囲は [0x20000000…0x3FFFFFFF](両端含む)である。

BIP 34、BIP 66、BIP 65によって課された制約のため、実際に利用可能なnVersion値は0x7FFFFFFB個しかない。これにより最大30件の独立した展開に制限される。上位3ビットを001に制限することで、本提案の目的のためにそのうち29件を確保し、(上位ビット010と011という)異なる仕組みによる将来の2件のアップグレードにも対応できる。ブロックのnVersionの上位ビットが001でない場合、展開の目的においてはすべてのビットが0であるものとして扱われる。

マイナーは、採用状況を可視化するためLOCKED_IN段階でも引き続きビットを設定すべきであるが、これはコンセンサスルールには影響しない。

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

各ソフトフォークの新しいコンセンサスルールは、ACTIVE状態にある各ブロックに対して強制される。

MUST_SIGNAL段階中に、リターゲット期間中で既に (2016 - threshold) ブロックがシグナリングに失敗している場合、それ以降にシグナリングに失敗するブロックはすべて無効となる。

状態遷移

BIP 8の状態遷移図

lockinontimeoutがtrueである場合、LOCKED_IN状態には遅くともtimeoutheightの高さまでに到達することに留意されたい。lockinontimeoutの値にかかわらず、LOCKED_INに到達した場合、ACTIVEには1リターゲット期間後、またはminimum_activation_heightのいずれか遅い方に到達する。

ジェネシスブロックは、定義上、各展開についてDEFINED状態にある。

State GetStateForBlock(block) {
    if (block.height == 0) {
        return DEFINED;
    }

リターゲット期間内のすべてのブロックは同一の状態を持つ。すなわち、floor(block1.height / 2016) = floor(block2.height / 2016) であれば、両者はすべての展開について同一の状態を持つことが保証される。

    if ((block.height % 2016) != 0) {
        return GetStateForBlock(block.parent);
    }

それ以外の場合、次の状態は直前の状態に依存する。

    switch (GetStateForBlock(GetAncestorAtHeight(block, block.height - 2016))) {

開始ブロック高に達するまで、初期状態にとどまる。

    case DEFINED:
        if (block.height >= startheight) {
            return STARTED;
        }
        return DEFINED;

STARTED状態の期間の後、設定されたビット数を集計し、直近の期間内で十分な数のブロックが展開ビットをバージョン番号に設定していればLOCKED_INへ遷移する。 しきい値に達しておらず、lockinontimeoutがtrueであり、かつタイムアウト前の最後の期間にある場合、MUST_SIGNALへ遷移する。 しきい値に達しておらずタイムアウトに達した場合、直接FAILEDへ遷移する。

ブロックの状態は自身のnVersionには依存せず、その祖先のnVersionのみに依存することに留意されたい。

    case STARTED:
        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;
        } else if (lockinontimeout && block.height + 2016 >= timeoutheight) {
            return MUST_SIGNAL;
        } else if (block.height >= timeoutheight) {
            return FAILED;
        }
        return STARTED;

MUST_SIGNALの期間を終えた場合、直接LOCKED_INへ遷移する。

    case MUST_SIGNAL:
        return LOCKED_IN;

LOCKED_INの少なくとも1リターゲット期間の後、最低起動高に達していれば自動的にACTIVEへ遷移する。それ以外の場合はLOCKED_INが継続する。

    case LOCKED_IN:
        if (block.height >= minimum_activation_height) {
            return ACTIVE;
        } else {
            return LOCKED_IN;
        }

ACTIVEとFAILEDは終端状態であり、展開は一度到達するとその状態にとどまる。

    case ACTIVE:
        return ACTIVE;

    case FAILED:
        return FAILED;
    }
}

実装 状態はブロックチェーンの各分岐に沿って保持されるが、リオーガナイゼーションが発生した場合には再計算が必要になりうることに留意されたい。

特定のブロック/展開の組み合わせについての状態は、現在のリターゲット期間より前の祖先(すなわち高さblock.height - 1 - (block.height % 2016) の祖先を含む)によって完全に決定されるため、2016の倍数となる各ブロックについて、その親をインデックスとして結果の状態をキャッシュすることで、上記の仕組みを効率的かつ安全に実装できる。

必須シグナリング

MUST_SIGNAL段階にある間に受信したブロックは、要求どおりにシグナリングしているかを検証しなければならない。例えば次のとおりである。

if (GetStateForBlock(block) == MUST_SIGNAL) {
    int nonsignal = 0;
    walk = block;
    while (true) {
        if ((walk.nVersion & 0xE0000000) != 0x20000000 || ((walk.nVersion >> bit) & 1) != 1) {
            ++nonsignal;
            if (nonsignal > 2016 - threshold) {
                return state.Invalid(BlockValidationResult::RECENT_CONSENSUS_CHANGE, "bad-version-bip8-must-signal");
            }
        }
        if (walk.nHeight % 2016 == 0) {
            // checked every block in this retarget period
            break;
        }
        walk = walk.parent;
    }
}

実装は、シグナリングを行わなかったこと(またはそうしたブロックの上に構築されたブロック)を理由に無効となったブロックを送ってきたピアを、バンしないよう注意すべきである。バンしてしまうと、準拠チェーンよりごくわずかに長いだけの非互換チェーンがp2pネットワークを分裂させる余地を生む。そのような事態が生じた場合、lockinontimeoutを設定していないノードは準拠チェーン上の新しいブロックを見ることができず、そのチェーンのほうが作業量が多くなった時点でも、そちらへリオーグしない。その結果、最も作業量の多い有効なチェーンに追随しないことになる。

lockinontimeoutをtrueに設定した実装は、lockinontimeoutをfalseに設定したノードよりも作業量の少ないチェーンを長期間追い続ける可能性がある。これがネットスプリットに至らないようにするには、lockinontimeoutをtrueに設定したノードは互いに優先的に接続する必要が生じうる。実装にlockinontimeoutをtrueに設定するよう提案する展開は、より作業量の多い代替チェーンが生じるリスクのないパラメーターを用いるか、あるいは当該展開をサポートする実装同士が優先的にピア接続する仕組みを規定すべきである。

警告機構

アップグレードの警告をサポートするため、「未知のアップグレード」を追加で追跡する。これには「暗黙のビット」マスク = (block.nVersion & ~expectedVersion) != 0を用いる。nVersionに予期しないビットが設定されている限り、このマスクは非ゼロになる。未知のアップグレードについてLOCKED_INが検出された場合、ソフトウェアは今後のソフトフォークについて、目立つように警告すべきである。次のリターゲット期間の後(未知のアップグレードがACTIVE状態にある時点)には、さらに強く警告すべきである。

getblocktemplateの変更

テンプレートリクエストオブジェクトは、以下の新しい項目を含むよう拡張される。

template request

キー必須型説明
rulesいいえ文字列の配列サポートするソフトフォーク展開の一覧(名前による)

テンプレートオブジェクトも同様に拡張される。

template

キー必須型説明
rulesはい文字列の配列active状態にあるソフトフォーク展開の一覧(名前による)
vbavailableはいオブジェクト保留中でサポートされるソフトフォーク展開の集合。それぞれソフトフォーク名をキー、ソフトフォークビットを値として用いる
vbrequiredいいえ数値サーバーが提出物に対して有効化を要求するソフトフォーク展開バージョンビットのビットマスク

テンプレートの “version” キーは維持され、サーバーの展開についての選好を示すために用いられる。versionbitsが使用されている場合、“version” はversionbitsの範囲 [0x20000000…0x3FFFFFFF] 内でなければならない。 マイナーは、特別な “mutable” キーを伴わずにブロックバージョンのビットをクリアまたは設定してよい。ただし、テンプレートの “vbavailable” に列挙されており、かつ(クリアを望む場合は)“vbrequired” にビットとして含まれていないことが条件である。 サーバーは、MUST_SIGNAL状態にある展開について “vbrequired” にビットを設定しなければならず、それにより生成されるブロックが有効であることを保証する。

“rules” または “vbavailable” のキーとして列挙されるソフトフォーク展開名には、’!’ 文字が前置される場合がある。 この接頭辞がない場合、GBTクライアントは、そのルールがそのままのテンプレートの使用に影響を与えないと仮定してよい。典型的な例は、これまで有効だったトランザクションが無効になる場合であり、BIP 16、65、66、68、112、113がこれに該当する。 クライアントが接頭辞なしのルールを理解しない場合、そのまま変更せずマイニングに用いてよい。 一方、この接頭辞が用いられている場合は、ブロック構造またはコインベーストランザクションへのより微妙な変更を示す。例としてはBIP 34(コインベースの構成を変更するため)や141(txidのハッシュ方式を変更し、コインベーストランザクションへのコミットメントを追加するため)が挙げられる。 ’!’ が前置されたルールを理解しないクライアントは、そのテンプレートを処理しようとしてはならず、変更せずともマイニングに用いようとしてはならない。

リファレンス実装

https://github.com/bitcoin/bitcoin/compare/master…luke-jr:bip8 (https://github.com/bitcoin/bitcoin/compare/master...luke-jr:bip8)

BIP 9との対比

  • lockinontimeoutフラグが追加され、LOCKED_INへの遷移を保証する手段を提供する。
  • 展開の単調クロックには、期間中央値時刻ではなくブロック高が用いられる。

後方互換性

BIP8とBIP9の展開は、同時に有効な展開ビットを共有すべきではない。BIP9のみを実装するノードは、ハッシュパワーのしきい値がtimeoutheightまでに達しなければBIP8のソフトフォークを起動しないが、そうしたノードも起動済みのノードが生成したブロックは引き続き受け入れる。

展開

展開提案の最新の一覧はこちらで確認できる。

参考文献

BIP9

メーリングリストでの議論 (https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-February/013643.html)

著作権

本文書はBSD 3-clauseライセンスおよびCreative Commons CC0 1.0 Universalのデュアルライセンスの下に置かれる。

変更履歴

  • 1.0.0(2026-08-03):
    • Completeへ進める。
  • 0.3.2(2021-02-17):
    • 起動しきい値を設定可能にする。
    • 推奨しきい値を95% から90% へ引き下げる。
    • ビット選択と命名規則に関する指針を更新する。
    • 経済的多数派によるアップグレードの想定を明確化する。
  • 0.3.1(2021-02-04):
    • MUST_SIGNAL検証チェックの疑似コードを簡略化する。
  • 0.3.0(2021-02-02):
    • LOCKED_IN中のシグナリングを必須ではなく推奨とする。
    • しきい値が満たされている限り、MUST_SIGNAL中に一部のブロックがシグナリングしないことを許容する。
  • 0.2.0(2020-10-17):
    • FAILING状態をMUST_SIGNAL段階に置き換える。
    • startheightとtimeoutheightをリターゲット境界とすることを要求する。
    • timeoutheightをstartheightから少なくとも4032ブロック後とすることを要求する。
    • lockinontimeout=trueのピア接続性に関する注記を追加する。
  • 0.1.0(2020-06-26):
    • 文書のステータスをDraftに戻す。
    • タイムアウトのロジックと状態遷移の順序を修正する。
    • minimum_activation_heightパラメーターを追加する。
    • MUST_SIGNAL状態に対応するようGBT節を更新する。
    • リファレンス実装へのリンクを追加する。
    • 疑似コードの初期化バグを修正する。
  • 0.0.2(2020-02-26):
    • 3年のタイムアウトによりRejectedへ移行する。
  • 0.0.1(2017-02-01):
    • 初回のDraftを公開する。