BIP 9 - 使用 version 位进行比特币软件升级

本文翻译自 https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki

译者:李康

摘要


该文献具体了对比特币区块结构中的 "version" 字段语义上提出的改变,允许多个后向兼容的改变 (称为"软分叉") 能够并行地部署。它依赖于将 "version" 字段解释为一个 bit vector,每一个 bit 可以被用来追踪一个独立的改变。它们在每一个重新调整难度的时候 (retarget period,即 2016 个区块) 被计数。一旦共识改变成功或者超时,有一个"冰冻"过程,过了这个冰冻过程,bit 就可以被再次使用来标识随后的软分叉。

动机


BIP 34 引进了一种机制来实施软分叉改变而不用一个预先定义好的时间戳 (或者区块高度标志),它依赖于矿工对新特性的支持度,由区块头部中的更高版本号的来预示。因为它依赖于将 version 作为整数来比较,所以它一次仅仅支持一个改变的实施,需要多个提案之间的相互协调,并且不允许永久的拒绝:只要一个软分叉没有完全实施,没有新的软分叉能够被调度实施。

还有,在它的 95% 阈值达到之后,BIP 34 使整数比较 (nVersion >= 2) 成为一个共识规则。从有效版本号集合中移除了 2^31 + 2 个值 (所有的负数,0 与 1,由于版本号被解释为有符号整数)。这表明了这种方法的另一个缺陷:每一次升级限制了允许的版本号集合。BIP 66 与 BIP 65 再次使用了 BIP 34, 从有效版本号集合中移除了版本号 2 与 3。如下面将要展示的,这是不必要的。

详细内容


每一个软分叉的部署通过下列的链参数具体化 (下面有进一步阐述):

  1. 名称 ("name") 指定了软分叉的简要描述,作为一个标识符使用是合理的。对于在一个 BIP 里描述的部署来说,推荐使用名称 "bipN",其中 N 是适当的 BIP 数字。

  2. 位 ("bit") 决定 version 字段中的哪一个 bit 被用来示意单个软分叉锁定 (lock-in) 与 激活 (activation) 状态。它从集合 {0, 1, 2, ... , 28}。

  3. "开始时间 (starttime)" 指定了最小的一个区块的中位时间 (median time),该时刻之后,指定的 bit 就获得了相应的意义。

  4. "超时时间 (timeout)" 指定了部署被认为失败的时刻。如果一个区块的中位时间 >= timeout,并且软分叉还没有锁定 (包含这个区块的 bit 状态),那么在所有接下来的子区块中,该部署都会被认为是失败的。

选择指南

下列指南被建议用来为一个软分叉选择上述参数:

  1. "name" 的选择应保证没有两个软分叉,并行,或者其它,曾经使用相同的名称.

  2. "bit" 的选择应该保证没有两个并行的软分叉使用相同的位。

  3. "starttime" 应该被设置为未来的某一个时刻,在包含该软分叉的软件发布版本之后的大约一个月时间。这允许一些发布版本的延迟,防止由于各方运行预发布软件而导致的触发。

  4. "timeout" 应该在开始时间的一年之后。

一个使用相同 "bit" 的后来的部署是可能的,只要开始时间在前一个软分叉的超时时间或者激活时间之后,但是如非必要,这是不提倡的,推荐两者之间 (新部署的开始时间与先前部署的超时时间或者激活时间,这两个部署使用相同的位) 有一个停顿时间以检测软件的 bug。

状态

对于每一个区块与软分叉,我们关联一个部署状态,可能的状态是:

  1. DEFINED 是每一个软分叉开始的第一个状态,对于每一个部署来说,创世区块都被预先定义为这个状态。

  2. STARTED 对于超出了开始时间的区块。

  3. LOCKED_IN,在状态为 STARTED 的区块之后的相应 bit 的计数在一个重新调整难度的周期内超过了阈值之后的一个重新调整难度周期称为锁定周期,看不懂没关系,下面有更详细的解释。

  4. ACTIVE 对于过了 LOCKED_IN 重新调整难度周期的所有区块。

  5. FAILED 对于超出超时时间的一个重新调整周期,如果 LOCKED_IN 没有达成的话。

位标志

区块头部的 nVersion 字段被解释为一个 32 位的小端整数,在该整数里选择的位对应的值是 (1 << N),其中 N 是选择的 bit 的位置。

处于 STARTED 状态的区块获得一个 nVersion,其中相应的 bit 被设置为 1。 这种区块的最高 3 位一定是 001,所以实际 nVersion 值的范围是 [0x20000000...0x3FFFFFFFF],闭区间。

由于 BIP 34,BIP 66 与 BIP 65 的限制,我们只有 0x7FFFFFFB 个可能的有效 nVersion 值。这限制了我们至多有 30 个独立的部署。通过限制最高 3 位为 001,对于这个提案的目的,我们可以得到 29 个可能的部署,并且支持未来两个不同机制的升级 (最高位为 010 与 011)。当一个区块 nVersion 高 3 位不是 001,对于部署的目的来说,它将被视为好像所有的位全部是0。

矿工应该在 LOCKED_IN 期间设置 bit,尽管这对共识规则没有影响。

新共识规则

对于处于 ACTIVE 状态的区块,每一个软分叉的新共识规则被强制执行。

状态转换

状态转换图

其中 MTP 是指一个区块的中位时间,参见 BIP 113

对于每一个部署,创世区块都有 DEFINEED 状态。

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))) {

在我们超过开始时间或者超时时间之前,我们保持在初始状态。代码中的 GetMedianTimePast 指向的是一个区块与它之前的 10 个区块的中位时间。表达式 GetMedianTimePast(block.parent) 指的就是上图中的 MTP,被视作为由链定义的单调的时钟。

case DEFINED:
            if (GetMedianTimePast(block.parent) >= timeout) {
                return FAILED;
            }
            if (GetMedianTimePast(block.parent) >= starttime) {
                return STARTED;
            }
            return DEFINED;

在处于 STARTED 状态的一个周期后,如果我们超过了超时时间,我们切换到 FAILED。如果没有,我们对 bits 的设置进行计数,如果在过去一个周期的区块中,在它们的版本号中有足够的部署位,则转移到 LOCKED_IN 状态。主网阈值是 >=1916 个区块 (95% of 2016),或者对于测试网络 >=1512 (75% of 2016)。到 FAILED 状态的转换优先发生,否则会出现模糊性。对于相同的 bit 可能有两个不重叠的部署,其中第一个转移到 LOCKED_IN 然而另外一个同时转移到 STARTED 状态,这种情况意味着两者都需要设置相同的 bit。

注意一个区块的状态从依赖于它自己的 nVersion,仅仅依赖它父区块以及祖父区块的 nVersion。

case STARTED:
            if (GetMedianTimePast(block.parent) >= timeout) {
                return FAILED;
            }
            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 状态维持一个周期之后,我们自动切换到 ACTIVE 状态。

case LOCKED_IN:
    return ACTIVE;

ACTIVE 与 FAILED 是中止状态,一旦他们达到,该部署就会一直维持该状态。

case ACTIVE:
    return ACTIVE;
case FAILED:
    return FAILED;
}
}

实现应该注意的是,状态是在区块链分支上维护的,当一个重组发生的时候,也许会需要重新计算。

一个具体区块/部署的状态完全由在当前调整周期之前的祖先决定 (一直到区块高度 block.height - 1 - (block.height%2016)),通过缓存高度是 2016 整数倍的区块可以有效、安全地实现该机制。

警告机制

为了支持升级警告,一个额外的 "未知的升级" 被追踪,使用 "implicit bit" mask = (block.nVersion & ~expectedVersion) != 0。一旦在 nVersion 中有未预料到的位被设置为 1,mask 将会是非零值。无论何时未知升级被检测到处于 LOCKED_IN 状态,软件应该警告用户即将到来的软分叉。在下一个重新调整周期之后 (即未知的升级处于激活状态),更应该强调警告用户。

getblocktemplate 改变

比特币矿机连接到矿池服务器的挖矿协议包括 Stratum (STM) 与 GetBlockTemplate (GBT)。

未来改变的支持


上述描述的机制是通用的,对于未来软分叉一些改变是可能的。这里是一些可以被考虑在内的注意。

修改的阈值 (Modified thresholds) 该 1916 阈值 (基于 BIP 34 的 95% 阈值) 不必永远保持,但是改变应该将告警系统的影响考虑在内。特别是,使用一个与告警系统阈值不兼容的 lock-in 阈值可能有长期的影响,因为告警系统不能再依赖一个永久可检测的条件。

冲突的软分叉 (Conflicting soft forks) 在某个时刻,两个相互排斥的软分叉也许会被提出。处理这个问题最天真的方式是从不创建实现两者的软件,但是这打赌至少一方被保证是丢失的。更好的方式是编码 "软分叉 X 不能被锁定" 作为冲突软分叉的共识规则 - 允许支持两者的软件,但是永远不会触发冲突的改变。

多阶段软分叉 (Multi-stage soft forks) 软分叉典型地被视为布尔值:他们从一个未激活状态到激活状态。或许,在某个时刻,有大量阶段的一个改变是需要的,一个接一个的验证规则被激活。上述机制能够被调整来支持这个,通过解释一个位集合作为一个整数,而不是隔离的位。告警系统是兼容此规则的,因为 (nVersion & ~nExpectedVersion) 对于增长的正数来说总是非零值。

设计原则


失败超时允许位的最终复用,即使一个软分叉从没有被激活。所以指向一个新 BIP 的位的新使用方法是清晰可见的。它故意被设计成粗粒度的,将合理的开发与部署延迟考虑进来。不太可能有足够的失败而导致位的短缺。

一个软分叉的冰冻时间允许客户端 bug 的一些检测,允许时间来警告用户,和成功软分叉的软件升级。

部署


一个现存的部署提案可以在这里发现。

存在的部署提案

译者注:软分叉成功激活后,会将相应的位移除,例如,CSV 软分叉成功激活后,标识该分叉的 0 号位元由 1 变为 0,即区块头部版本号由 0x20000001 变为 0x20000001。 更多相关信息请参考

  1. CSV 分叉

  2. version-bits-miners-faq

  3. coinbase CEO 对于比特币升级的看法

results matching ""

    No results matching ""