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

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 または起動よりも後であれば可能だが、必要になるまでは推奨されず、必要になった場合でも、不具合のあるソフトウェアを検出するための間隔を設けることが推奨される。

startheighttimeoutheightminimum_activation_height は 2016 の正確な倍数(すなわちリターゲット境界)でなければならず、timeoutheightstartheight から少なくとも 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 を公開する。