第十二章. 挖矿和共识算法 #2

  • berry
  • 发布于 2025-02-09 10:32
  • 阅读 9

挖掘区块

现在,由 Jing 的节点构建了一个候选块,是时候让 Jing 的硬件挖矿设备来“挖”这个块,以找到使该块有效的工作证明算法的解决方案。在本书中,我们已经研究了密码哈希函数在比特币系统各个方面的应用。SHA256 哈希函数是比特币挖矿过程中使用的函数。

简单来说,挖矿是反复对候选块头部进行哈希,不断更改一个参数,直到生成的哈希值匹配特定目标。哈希函数的结果无法提前确定,也无法创建一个能够产生特定哈希值的模式。哈希函数的这一特性意味着,要想产生与特定目标匹配的哈希结果,唯一的方法就是不断尝试,修改输入,直到所需的哈希结果出现为止。

工作量证明算法(PoW)

哈希算法接受任意长度的数据输入,并产生一个固定长度的确定性结果,称为摘要。摘要是对输入的数字承诺。对于任何特定的输入,生成的摘要将始终相同,并且可以轻松地由实施相同哈希算法的任何人进行计算和验证。密码哈希算法的一个关键特征是,找到产生相同摘要的两个不同输入是计算上不可行的(称为碰撞)。作为推论,选择一种输入以产生所需的摘要也几乎是不可能的,除非尝试随机输入。

使用SHA256,输出始终是256位长,而不考虑输入的大小。例如,我们将计算短语“Hello, World!”的SHA256哈希值:

$ echo "Hello, world!" | sha256sum
d9014c4624844aa5bac314773d6b689ad467fa4e1d1a50a1b8a99d5a95f72ff5 -

这个256位的输出(用十六进制表示)是该短语的哈希或摘要,并且依赖于短语的每个部分。添加一个字母、标点符号或任何其他字符将产生一个不同的哈希。

在这种情况下使用的一个变量被称为 nonce。Nonce 用于改变密码函数的输出,这里用于改变 SHA256 对该短语的摘要输出。

为了从这个算法中提出一个挑战,让我们设定一个目标:找到一个短语,它产生一个十六进制哈希,以零开头。幸运的是,这并不困难,如示例 12-4 所示。

示例 12-4. 简单的工作量证明实现

$ for nonce in $( seq 100 ) ; do echo "Hello, world! $nonce" | sha256sum ; done
3194835d60e85bf7f728f3e3f4e4e1f5c752398cbcc5c45e048e4dbcae6be782 -
bfa474bbe2d9626f578d7d8c3acc1b604ec4a7052b188453565a3c77df41b79e -
[...]
f75a100821c34c84395403afd1a8135f685ca69ccf4168e61a90e50f47552f61 -
09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314 -

短语“Hello, World! 32”产生了以下哈希,符合我们的标准:09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314。找到它花费了32次尝试。从概率上讲,如果哈希函数的输出是均匀分布的,我们预计每16次哈希(从0到F的16个十六进制数字中的一个)就会找到一个以0开头的结果。用数字表示,这意味着找到的哈希值小于0x1000000000000000000000000000000000000000000000000000000000000000。我们将这个阈值称为目标,目标是找到一个数字上小于目标的哈希。如果我们降低目标,找到一个小于目标的哈希的任务就会变得越来越困难。

为了给出一个简单的类比,想象一场游戏,玩家反复投掷一对骰子,试图得到小于指定目标的点数。在第一轮中,目标是12。除非你掷出双6,否则你就赢了。在下一轮中,目标是11。玩家必须掷出10或更少的点才能赢,这又是一个容易的任务。假设几轮后,目标降到了5。现在,超过一半的骰子投掷将超过目标,因此无效。要赢得的次数越低,掷骰子的次数就越多。最终,当目标是2(可能的最小值)时,每36次投掷中只有一次,约占3%,会产生一个成功的结果。

从知道骰子游戏目标是3的观察者的角度来看,如果有人成功地进行了一次投掷,可以假设他们平均尝试了36次。换句话说,可以从目标所施加的困难来估计成功所需的工作量。当算法基于SHA256等确定性函数时,输入本身就构成了证明,证明了为了产生低于目标的结果所做的一定量工作。因此,工作量证明。

注意:尽管每次尝试都会产生随机结果,但任何可能结果的概率都可以事先计算。因此,指定难度的结果构成了特定工作量的证明。

在示例12-4中,获胜的“nonce”是32,这个结果可以由任何人独立确认。任何人都可以将数字32添加为短语“Hello, world!”的后缀并计算哈希,以验证其是否小于目标:

$ echo "Hello, world! 32" | sha256sum
09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314 -

虽然验证只需一次哈希计算,但我们需要32次哈希计算才能找到有效的 nonce。如果我们有一个更低的目标(更高的难度),那么需要进行更多的哈希计算才能找到合适的 nonce,但任何人只需要一次哈希计算来验证。通过知道目标,任何人都可以利用统计数据估计难度,从而大致了解需要多少工作量才能找到这样的 nonce。

注意:工作量证明必须产生一个小于目标的哈希。更高的目标意味着更容易找到小于目标的哈希。更低的目标意味着更难找到小于目标的哈希。目标和难度是反相关的。

比特币的工作量证明与示例12-4中显示的挑战非常相似。矿工构建一个填充了交易的候选块。然后,矿工计算此块头的哈希,并查看其是否小于当前目标。如果哈希不小于目标,则矿工将修改 nonce(通常只是递增一个)并重试。在比特币网络的当前难度下,矿工必须尝试大量次数才能找到一个导致块头哈希低到足够的 nonce。

目标表示

区块头中包含的目标值使用一种称为“目标位”或简称“位”(bits)的记法表示,在第277,316个区块中的值为0x1903a30c。这种记法将工作量证明目标表达为一个系数/指数格式,其中前两个十六进制数字表示指数,接下来的六个十六进制数字表示系数。因此,在这个区块中,指数为0x19,系数为0x03a30c。

从这种表示中计算难度目标的公式是:

$$ target = coefficient 2^{(8 (exponent -3))} $$

使用该公式,以及难度位值为0x1903a30c,我们得到:

$$ target = 0x03a30c 2^{(0x08 (0x19 - 0x03))} $$

结果为:

22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328

或者,用十六进制表示:

0x0000000000000003A30C00000000000000000000000000000000000000000000

这意味着对于高度为277,316的有效区块,其区块头哈希必须小于目标值。在二进制中,该数字必须有超过60个前导零位。在这个难度水平下,每秒处理1万亿次哈希(1 TH/sec)的单个矿工平均每8,496个区块或平均每59天才能找到一个解决方案。

调整难度的重新定位

正如我们所见,目标确定了难度,因此影响了寻找解决方案的时间。这引出了一个显而易见的问题:为什么需要调整难度,谁来进行调整,以及如何进行调整呢?

比特币的区块平均每 10 分钟产生一个。这是比特币的节拍,支撑着货币发行频率和交易结算速度。它不仅在短期内必须保持恒定,而且在数十年的时间跨度内也必须保持稳定。随着时间的推移,预计计算机算力将继续快速增长。此外,参与挖矿的参与者数量和他们使用的计算机也将不断变化。为了保持区块生成时间在 10 分钟左右,挖矿的难度必须进行调整,以适应这些变化。 实际上,工作证明目标是一个动态参数,定期调整以满足每 10 分钟一个区块的目标。简单来说,目标被设置为当前挖矿算力会产生 10 分钟一个区块的时间间隔。

那么,在完全去中心化的网络中如何进行这样的调整呢?重新调整难度在每个节点上都会自动进行。每产生 2,016 个区块,所有节点都会重新调整工作证明。计算实际时间跨度与期望的每个区块 10 分钟时间跨度之间的比例,并对目标进行相应的调整(增加或减少)。简单来说:如果网络发现区块生成速度比每 10 分钟快,难度会增加(目标减少)。如果区块的发现速度比预期的慢,难度会减少(目标增加)。

该方程可以总结为:

新目标 = 旧目标 *(上一个 2,015 个区块的实际时间 / 20,160 分钟 ) 【<mark style="color:red;">原翻译:New Target = Old Target * (20,160 minutes / Actual Time of Last 2015 Blocks), 个人认为是写错了</mark>,即新目标 = 旧目标 *( 20,160 分钟 / 上一个 2,015 个区块的实际时间 ),因为如果实际时间小,说明需要提升难度,即减少新目标的值。而且下面代码也是先乘以实际值再除以20160分钟。】

注意:尽管每产生 2,016 个区块就进行一次目标校准,但由于比特币软件中的一个偏移错误,实际上是基于前 2,015 个区块的总时间(而不是应该的 2,016 个区块),导致难度朝更高方向偏差 0.05%。

示例 12-5 展示了比特币核心客户端使用的代码。

示例 12-5. 调整工作证明目标:pow.cpp 中的 CalculateNextWorkRequired()

// Limit adjustment step
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
LogPrintf(" nActualTimespan = %d before bounds\n", nActualTimespan);
if (nActualTimespan &lt; params.nPowTargetTimespan/4)
    nActualTimespan = params.nPowTargetTimespan/4;
if (nActualTimespan > params.nPowTargetTimespan*4)
    nActualTimespan = params.nPowTargetTimespan*4;
// Retarget
const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
arith_uint256 bnNew;
arith_uint256 bnOld;
bnNew.SetCompact(pindexLast->nBits);
bnOld = bnNew;
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;

if (bnNew > bnPowLimit)
    bnNew = bnPowLimit;

为了避免难度的极端波动,每个周期内的重新调整幅度必须小于四倍(4)。如果所需的目标调整超过四倍,它将被调整为四倍,不会更多。任何进一步的调整将在下一个重新调整周期内完成,因为不平衡将持续到接下来的2,016个区块。因此,哈希功率和难度之间的巨大差异可能需要多个2,016个区块周期来平衡。

请注意,目标与交易数量或交易价值无关。这意味着用于保护比特币的哈希功率和因此消耗的电力完全独立于交易数量。比特币可以扩展并保持安全性,而无需从当前水平增加哈希功率。哈希功率的增加代表了市场力量,因为新的矿工进入市场。只要足够多的哈希功率由诚实的矿工控制以追求奖励,就足以防止“接管”攻击,因此足以保护比特币。

挖矿的难度与电力成本和比特币与用于支付电力的货币的汇率密切相关。高性能的挖矿系统在当前一代硅制造技术下尽可能高效,以最高速率将电力转换为哈希计算。对挖矿市场的主要影响是一千瓦时电力的价格与比特币之间的汇率,因为这决定了挖矿的盈利能力,从而影响了进入或退出挖矿市场的激励。

使用cpuminer进行挖矿

默认情况下,Bitcoin 全节点bitcoind)不会自动挖矿。运行全节点的主要功能是维护和验证区块链,而不是参与挖矿。全节点的职责是接收、验证、转发交易和区块,以及存储整个区块链数据。挖矿需要大量的计算资源,而大多数运行全节点的用户只是为了支持网络或进行钱包管理,不是为了挖矿。

所以如果启动了全节点后,如果往全节点发送交易的时候,我们可以观察到交易能进入到mempool,但并没有出块。有两种方式,能够触发挖矿。

  1. 使用 bitcoin-cli generatetoaddress \<nblocks> \<address>,通过rpc接口调用全节点的GenerateBlocks进行挖矿。这种方式只适合在测试环境或者自定义网络里进行挖矿,在主网几乎不太可能能挖到块。
  2. 使用专门为比特币或其他基于 SHA256 算法的加密货币设计的挖矿软件,如软件cpuminer,可以通过 getblocktemplate 连接比特币节点,进行 Solo 挖矿,或者通过矿池的 Stratum 协议与矿池合作。

下面介绍从cpuminer源码编译,并连接到本地运行的全节点进行solo挖矿。

$mkdir -p /data/miner_workspace/src/github.com/pooler
$git clone git@github.com:pooler/cpuminer.git
$cd cpuminer
$./autogen.sh
$./nomacro.pl
$./configure CFLAGS="-O3"
$make

结束后,当前目录生成程序minerd。

但是cpuminer需要获取blocktemplate才能挖矿,如果运行自定义网络只运行一个节点是没办法获取到的,调用getblocktemplate会返回not connected错误。

$bitcoin-cli -rpcuser=user -rpcpassword=pass -rpcconnect=localhost -rpcport=8332 getblocktemplate '{"rules": ["segwit"]}'
error code: -10
error message:
Bitcoin Core is in initial sync and waiting for blocks...

所以还需要额外运行另外一个节点,并连接到当前节点。

$bitcoind --conf=/data/bitcoin/bitcoin.conf -daemon -reindex -addnode=&lt;机器1的ip>:8333

此刻,因为网络区块高度为0,如果再调用getblocktemplate,会提示区块仍然同步中。

$bitcoin-cli -rpcuser=user -rpcpassword=pass -rpcconnect=localhost -rpcport=8332 getblocktemplate '{"rules": ["segwit"]}'
error code: -10
error message:
Bitcoin Core is in initial sync and waiting for blocks...

此刻需要通过generatetoaddress生成至少一个块,就可以查询到blocktemplate使用cpuminer进行挖矿。

$bitcoin-cli -rpcuser=user -rpcpassword=pass -rpcconnect=localhost -rpcport=8332 generatetoaddress 1 &lt;address>
$bitcoin-cli -rpcuser=user -rpcpassword=pass -rpcconnect=localhost -rpcport=8332 getblocktemplate '{"rules": ["segwit"]}'
{
  "capabilities": [
    "proposal"
  ],
  "version": 536870912,
  ......
}

通过minerd连接到本地节点进行挖矿,在本地成功计算出nonce后,会调用submitblock,提交块到当前全节点。

$/data/miner_workspace/src/github.com/pooler/cpuminer/minerd --url=http://127.0.0.1:8332 --userpass=user:pass --coinbase-addr=&lt;address> --protocol-dump
> POST / HTTP/1.1
Host: 127.0.0.1:8332
Authorization: Basic YmVycnk6MTIzNDU2
Accept-Encoding: deflate, gzip, br, zstd
Content-Type: application/json
User-Agent: cpuminer/2.5.1
X-Mining-Extensions: midstate
Content-Length: 230

[2024-11-23 17:57:14] thread 3: 24 hashes, 9.68 khash/s
[2024-11-23 17:57:14] thread 2: 24 hashes, 9.18 khash/s
[2024-11-23 17:57:14] thread 1: 24 hashes, 9.44 khash/s
[2024-11-23 17:57:14] thread 0: 24 hashes, 9.01 khash/s
[2024-11-23 17:57:14] JSON protocol request:
{"method": "submitblock", "params": ["00000020259b13a6f59da1bd307ecac4b4280fbb1217a4982cf23f869bd1f34af3608967ac4c87b4c999bad534076fae39980dd64c7bfc3f8845b5a365da32b011e055e7faa64167ffff7f20bfffffff0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025200ffffffff01002f6859000000001976a914a6cc6d3896e4181ff3608b15972e22f4d31523a088ac00000000"], "id":1}

如果创世难度设置很低的话,瞬间出完2015块后,网络难度会自动调整成非常高,返回high-hash结果,当前算力就挖不出来。这就需要使用特定的挖矿机器或者gpu,加入到矿池进行挖矿。

* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Content-Type: application/json
&lt; Date: Sat, 23 Nov 2024 09:53:07 GMT
&lt; Content-Length: 43
&lt;
* Connection #1 to host 127.0.0.1 left intact
[2024-11-23 09:53:07] JSON protocol response:
{
   "error": null,
   "result": "high-hash",
   "id": 1
}

中位时间过去(Median-Time-Past, MPT)

在比特币中,墙上时间和共识时间之间存在微妙但非常重要的区别。比特币是一个去中心化的网络,这意味着每个参与者都有自己的时间观念。网络上的事件不会在所有地方即时发生。必须考虑网络延迟,以适应每个节点的时间观点。最终,一切都会同步,形成一个共同的区块链。 比特币大约每10分钟就会就过去的区块链状态达成共识。

区块头中设置的时间戳由矿工设置。共识规则允许一定程度的灵活性,以解决去中心化节点之间的时钟精度差异。然而,这给矿工造成了一个不利的激励,即在区块中虚报时间。例如,如果矿工将时间设定在未来,他们可以降低难度,从而可以挖掘更多区块并声称一部分留给未来矿工的区块补贴。如果他们可以将某些区块的时间设定在过去,那么他们可以将当前时间用于其他区块,从而再次使区块之间的时间看起来很长,以操纵难度。

为了防止操纵,比特币有两个共识规则。第一个是,没有节点将接受任何时间超过两小时的区块。第二个是,没有节点将接受一个时间小于或等于最近的11个区块的中位数时间的区块,称为过去中位数时间(MTP)。

作为BIP68相对时间锁定激活的一部分,对交易中的时间(绝对和相对)计算方式也进行了更改。以前,矿工可以在区块中包含任何具有与该区块相等或更早的时间锁定的交易。这促使矿工使用他们认为可能的最新时间(接近两小时的未来)以便更多的交易符合他们的区块。

为了消除虚报的激励并加强时间锁的安全性,BIP113被提出,并在相对时间锁的BIP同时激活。MTP成为所有时间锁计算中使用的共识时间。通过从大约过去两小时开始计算中点,减少了任何一个区块时间戳的影响。通过整合11个区块,没有单个矿工可以影响时间戳,以获得尚未成熟的具有时间锁的交易的费用。

MTP改变了锁定时间、CLTV、序列和CSV的时间计算实现。由MTP计算的共识时间通常比墙上时钟时间晚约一小时。如果你创建时间锁定交易,请在估算要编码的锁定时间、序列、CLTV和CSV的期望值时予以考虑。

成功挖掘区块

如前所述,Jing的节点已经构建了一个候选区块并准备好进行挖矿。Jing拥有多个硬件挖矿设备,其中包括专用的应用特定集成电路,数十万个集成电路以惊人的速度并行运行比特币的双 SHA256 算法。许多这些专用机器通过USB或局域网连接到他的挖矿节点。接下来,运行在Jing桌面上的挖矿节点将区块头传输到他的挖矿硬件上,开始每秒测试数万亿种不同的区块头变化。由于nonce只有32位,挖矿硬件在耗尽所有nonce可能性(约40亿个)后,会更改区块头(调整coinbase额外的nonce空间、版本位或时间戳),并重置nonce计数器,测试新的组合。

大约在开始挖掘特定区块后的11分钟内,其中一台挖矿机找到了一个解决方案,并将其发送回挖矿节点。

立即,Jing的挖矿节点将该区块传输给所有的对等节点。它们接收、验证,然后传播新的区块。随着区块在网络中扩散,每个节点都将其添加到自己的区块链副本中,将其扩展到新的高度。当挖矿节点接收并验证区块时,它们放弃了在相同高度寻找区块的努力,并立即开始计算链中的下一个区块,使用Jing的区块作为“父区块”。通过在Jing新发现的区块之上构建,其他矿工实际上是在利用他们的挖矿算力来背书Jing的区块和其延伸的链。

在下一节中,我们将看到每个节点用于验证区块并选择最难链的过程,从而形成构成去中心化区块链的共识。

验证新区块

比特币共识机制的第三步是网络中每个节点对每个新区块进行独立验证。随着新解决的区块在网络中传播,每个节点都会执行一系列测试来验证它。独立验证还确保只有遵循共识规则的区块才会被纳入区块链中,从而使其矿工获得奖励。违反规则的区块将被拒绝,不仅会使其矿工失去奖励,还会浪费寻找工作证明解决方案的努力,因此给这些矿工带来了创建区块的所有成本,但却没有给予他们任何奖励。

当一个节点收到一个新区块时,它将通过检查一个必须满足所有条件的长列表来验证该区块;否则,该区块将被拒绝。这些条件可以在比特币核心客户端的 CheckBlock 和 CheckBlockHeader 函数中看到,其中包括:

• 区块数据结构在语法上是有效的。

• 区块头哈希小于目标值(实施工作证明)。

• 区块时间戳位于 MTP 和未来两小时之间(允许时间误差)。

• 区块权重在可接受范围内。

• 第一个交易(仅第一个)是 coinbase 交易。

• 区块中的所有交易都使用“交易独立验证”部分讨论的交易清单进行验证。

网络中每个节点对每个新区块的独立验证确保了矿工无法作弊。在前面的部分中,我们看到矿工可以编写一个交易,以奖励他们在区块内创建的新比特币并获取交易费。为什么矿工不会为自己编写一个价值一千个比特币的交易,而是正确的奖励?因为每个节点都根据相同的规则验证区块。无效的 coinbase 交易会使整个区块无效,导致该区块被拒绝,因此该交易永远不会成为区块链的一部分。矿工必须根据所有节点遵循的共享规则构建一个区块,并使用正确的 PoW 解决方案对其进行挖掘。为此,他们在挖掘过程中耗费了大量的电力,如果他们作弊,所有的电力和努力都将被浪费。这就是为什么独立验证是分散式共识的关键组成部分。

组装和选择区块链

比特币的去中心化共识机制的最后一部分是将区块组装成链,并选择具有最多工作量证明的链。 最佳的区块链是具有最多累积工作量证明的所有有效区块链。

_最佳链_也可能有与最佳链上的区块“同级”的分支。这些区块是有效的,但不是最佳链的一部分。它们被保留供将来参考,以防这些次要链中的任何一个后来成为主要链。当出现同级区块时,通常是由于在相同高度几乎同时挖掘不同区块造成的。

当收到一个新区块时,节点将尝试将其添加到现有的区块链上。节点将查看区块的“上一个区块哈希”字段,该字段是指向区块父级的引用。然后,节点将尝试在现有的区块链中找到该父级。大多数情况下,父级将是最佳链的“尖端”,这意味着这个新区块扩展了最佳链。

有时,新区块并不会扩展最佳链。在这种情况下,节点将新区块的头部连接到一个次要链上,然后将次要链的工作与先前的最佳链进行比较。如果次要链现在是最佳链,节点将相应地重新组织其已确认交易和可用UTXO的视图。如果节点是矿工,它现在将构建一个候选区块,扩展这个新的,具有更多工作量证明的链。

通过选择具有最大累积工作量的有效链,所有节点最终达成网络范围内的共识。随着更多的工作被添加,最终解决了链之间的临时差异,从而扩展了可能的链。

注意:本节中描述的区块链分叉是由全球网络中的传输延迟自然发生的结果。我们也将在本章后面看到故意引发的分叉

分叉几乎总是在一个区块内解决的。如果两个区块几乎同时被位于前一个分叉的相反“方向”的矿工发现,那么意外的分叉可能会延伸到两个区块。然而,这种情况发生的几率很低。

比特币的区块间隔为10分钟,是快速确认时间和分叉概率之间的设计妥协。更快的区块时间会使交易似乎更快得到确认,但会导致区块链分叉更频繁,而更慢的区块时间会减少分叉的数量,但使结算速度看起来更慢。

注意:哪种更安全:一个交易包含在平均每10分钟一个区块的区块中,还是一个交易包含在已建立在其上的九个区块的区块中,平均每个区块之间的时间是一分钟?答案是它们同样安全。想要进行双重支付的恶意矿工需要做的工作量与总网络算力的10分钟相等,以创建具有相同工作证明的链。

区块之间时间缩短并不意味着更早的结算。它的唯一好处是为那些愿意接受这些保证的人提供更弱的保证。例如,如果你愿意接受矿工在三分钟内就同意了最佳区块链作为足够的安全性,那么你会更喜欢一个每分钟一个区块的系统,你可以等待三个区块,而不是一个每10分钟一个区块的系统。区块之间的时间越短,越容易因意外分叉而浪费矿工的工作量(除其他问题外),因此许多人更喜欢比特币的10分钟区块而不是更短的区块间隔。

挖矿和哈希彩票

比特币挖矿是一个极具竞争性的行业。比特币的哈希算力每年呈指数增长。有些年份的增长反映了技术的完全变革,比如2010年和2011年,许多矿工从使用CPU挖矿转向了GPU挖矿和可编程门阵列(FPGA)挖矿。2013年ASIC挖矿的引入又使挖矿能力实现了另一个巨大的飞跃,通过将双SHA256函数直接置于专门用于挖矿目的的硅片上。最早的这种芯片在一个单独的盒子中就能提供比2010年整个比特币网络的挖矿能力还要多。

截至目前为止,人们认为比特币挖矿设备已经没有更大的飞跃了,因为该行业已经达到了摩尔定律的前沿,摩尔定律规定计算密度大约每18个月翻一番。尽管如此,网络的挖矿能力仍在快速增长。

额外的Nonce解决方案

自2012年以来,挖矿已经发展出一种解决区块头结构中的一个基本限制的方法。在比特币的早期阶段,挖矿者可以通过迭代Nonce直到得到的哈希值低于目标值来找到一个区块。随着难度的增加,挖矿者经常会循环遍历所有40亿个Nonce值,但仍然找不到一个区块。然而,这个问题很容易通过更新区块时间戳以考虑经过的时间来解决。因为时间戳是头部的一部分,改变时间戳会允许挖矿者再次迭代Nonce值,从而产生不同的结果。然而,一旦挖矿硬件超过4GH/s,这种方法就变得越来越困难,因为Nonce值在不到一秒钟的时间内就会被耗尽。随着ASIC挖矿设备开始超过TH/s的哈希率,挖矿软件需要更多的Nonce值空间才能找到有效的区块。时间戳可以被延长一点,但如果将其移动得太远,将会导致区块无效。区块头需要一种新的变化来源。

一个被广泛采用的解决方案是使用coinbase交易作为额外Nonce值的来源。因为coinbase脚本可以存储2到100字节的数据,挖矿者开始使用这个空间作为额外Nonce空间,从而使他们能够探索更大范围的区块头值以找到有效的区块。coinbase交易包含在默克尔树中,这意味着coinbase脚本的任何更改都会导致默克尔根的变化。8字节的额外Nonce加上4字节的“标准”Nonce,使得挖矿者每秒可以探索总共2^96(8后跟28个零)种可能性,而无需修改时间戳。

今天广泛使用的另一种解决方案是利用区块头版本位字段的最多16位用于挖矿,这就是BIP320中描述的方法。如果每台挖矿设备都有自己的coinbase交易,那么通过仅对区块头进行更改,这使得个别挖矿设备可以达到高达281 TH/s的哈希速率。这比每40亿个哈希值增加一次coinbase交易中的额外Nonce更简单,后者需要重新计算默克尔树的整个左侧直到根节点。

挖矿池

矿工们在这个竞争激烈的环境中,单独工作的个体矿工(也称为独立矿工)几乎没有机会。他们找到一个足以抵消电费和硬件成本的区块的可能性非常低,这就像是在赌博,就像是在买彩票一样。即使是最快的消费者 ASIC 挖矿系统也无法与商业运营相比,商业运营可以将数以万计的这些系统堆叠在靠近发电站的巨大仓库中。许多矿工现在合作组成矿池,将他们的算力汇集在一起,分享奖励给成千上万的参与者。通过参与矿池,矿工能够获得总奖励的一个较小份额,但通常每天都会获得奖励,减少了不确定性。

让我们看一个具体的例子。假设一个矿工购买了一批挖矿硬件,总算力相当于当前总网络算力的 0.0001%。如果协议难度永远不变,那么该矿工将大约需要 20 年的时间才能找到一个新的区块。这可能是一个漫长的等待时间来获得报酬。然而,如果该矿工与其他矿工一起在一个矿池中合作,这些矿工的总算力相当于总网络算力的 1%,他们平均每天将会挖到超过一个区块。该矿工将只会收到他们在奖励中的一部分份额(减去矿池收取的任何费用),因此他们每天只会收到一小部分奖励。如果他们每天挖矿 20 年,他们将获得与自己独立挖到平均区块相同的金额(不考虑矿池费用)。唯一的基本区别在于他们收到支付的频率。

挖矿池通过专门的矿池挖矿协议协调着数百甚至数千名矿工。矿工们将他们的挖矿设备配置为连接到矿池服务器,之前需要在矿池创建一个帐户。他们的挖矿硬件在挖矿时保持连接到矿池服务器,与其他矿工同步他们的努力。因此,矿池矿工共同努力挖掘一个区块,然后分享奖励。

成功的区块将奖励支付给矿池的比特币地址,而不是给个体矿工。一旦矿工的奖励份额达到了一定的阈值,矿池服务器将定期向矿工的比特币地址进行支付。通常,矿池服务器会收取一定比例的奖励作为提供矿池挖矿服务的费用。

参与矿池的矿工分担搜索候选区块解决方案的工作,根据其挖矿贡献获得“份额”。挖矿池为获得份额设置了更高的目标(更低的难度),通常比比特币网络的目标容易多达1000倍。当矿池中的某个人成功挖掘了一个区块时,奖励由矿池获得,然后按照各个矿工贡献的份额比例与所有矿工分享。

许多矿池对任何矿工都是开放的,无论其规模大小、专业性或业余性。因此,矿池的参与者可能有些人只有一台小型的挖矿机器,而其他人可能拥有一间装满高端挖矿硬件的车库。有些人可能只使用几十千瓦的电力进行挖矿,而另一些人可能运行着消耗兆瓦电力的数据中心。矿池如何衡量个人的贡献,以公平地分配奖励,又不会出现作弊的可能性呢?答案是利用比特币的工作证明算法来衡量每个矿池矿工的贡献,但设置较低的难度,以便即使最小的矿池矿工也能够频繁地赢得份额,从而有利于向矿池做出贡献。通过为赢得份额设置较低的难度,矿池衡量了每个矿工的工作量。每当矿池矿工找到一个区块头散列小于矿池目标的情况,他们就证明了他们已经完成了寻找该结果的散列工作。该区块头最终会提交给coinbase交易,并可用于证明该矿工使用了一个将区块奖励支付给矿池的coinbase交易。每个矿池矿工都被分配一个略有不同的coinbase交易模板,以便每个人都散列不同的候选区块头,防止工作的重复。

寻找份额的工作以一种在统计上可衡量的方式,有助于寻找一个低于比特币网络目标的散列的整体工作。数千个矿工试图寻找低价值的散列,最终将会找到一个足够低以满足比特币网络的目标。

让我们回到掷骰子游戏的类比。如果骰子玩家的目标是投掷小于四(总体网络难度)的骰子,那么矿池将设置一个更容易的目标,计算矿池玩家成功投掷小于八的次数。当矿池玩家投掷小于八(矿池份额目标)时,他们赢得份额,但他们并没有赢得游戏,因为他们没有达到游戏目标(小于四)。矿池玩家往往会更频繁地实现更容易的矿池目标,即使他们没有实现赢得游戏的更难的目标。偶尔,矿池玩家中的一名将会掷出一个小于四的组合点数,从而矿池获胜。然后,可以根据他们赢得的份额将收益分配给矿池玩家。

同样,一个挖矿池将设置一个(更高、更容易的)矿池目标,以确保个人矿池矿工通过找到区块头散列小于矿池目标而经常赢得份额。偶尔,这些尝试中的一个将产生一个区块头散列小于比特币网络目标的情况,使其成为一个有效的区块,整个矿池都赢得了胜利。

托管矿池

大多数矿池都是“托管”的,这意味着有一个公司或个人在运行一个矿池服务器。矿池服务器的所有者被称为矿池操作员,他们会向矿工收取一定比例的费用作为矿池收益。

矿池服务器运行着专门的软件和矿池挖矿协议,协调着矿池矿工的活动。矿池服务器还连接到一个或多个完整的比特币节点。这使得矿池服务器能够代表矿工验证区块和交易,减轻了他们运行完整节点的负担。对于一些矿工来说,能够在不运行完整节点的情况下进行挖矿是加入托管矿池的另一个好处。

矿池矿工使用诸如Stratum(版本1或版本2)之类的挖矿协议连接到矿池服务器。Stratum v1会创建包含候选区块头的区块模板。矿池服务器通过聚合交易、添加一个coinbase交易(带有额外的nonce空间)、计算默克尔根,并链接到上一个区块的哈希来构建候选区块。然后,候选区块的头部作为模板发送给每个矿池矿工。每个矿池矿工使用区块模板进行挖矿,目标难度比比特币网络的目标难度更高(更容易),并将任何成功的结果发送回矿池服务器以获取份额。

Stratum v2还可以选择允许矿池中的个别矿工选择哪些交易出现在他们自己的区块中,他们可以使用自己的完整节点来选择。

点对点挖矿矿池(P2Pool)

管理型矿池使用 Stratum v1 存在欺诈的可能性,因为矿池操作者可能会指导矿池努力进行双花交易或使块无效(见第288页的“算力攻击”)。此外,集中式矿池服务器代表了单点故障。如果矿池服务器宕机或受到拒绝服务攻击的影响,矿池矿工将无法进行挖矿。为了解决这些集中化问题,2011年提出并实施了一种新的矿池挖矿方法:点对点挖矿矿池(P2Pool),即没有中央操作者的点对点挖矿矿池。

P2Pool 通过分散矿池服务器的功能,实施了一个称为共享链(share chain)的并行类似区块链的系统。共享链是以比比特币区块链更低的难度运行的区块链。共享链允许矿池矿工通过在共享链上每30秒挖掘一个共享块来协作于一个去中心化矿池中。共享链上的每个块记录了为贡献工作的矿池矿工分配的比例份额奖励,并从上一个共享块中延续这些份额。当共享链中的一个共享块也达到比特币网络目标时,它会被传播并包含在比特币区块链上,奖励为所有贡献到所有先前的共享块的矿池矿工。基本上,共享链允许所有矿池矿工使用类似比特币区块链的去中心化共识机制来跟踪所有份额。

P2Pool 挖矿比矿池挖矿更复杂,因为它要求矿池矿工运行一台具有足够磁盘空间、内存和互联网带宽以支持比特币全节点和 P2Pool 节点软件的专用计算机。P2Pool 矿工将他们的挖矿硬件连接到本地的 P2Pool 节点,该节点通过向挖矿硬件发送区块模板来模拟矿池服务器的功能。在 P2Pool 上,个别矿池矿工构建自己的候选块,聚合交易,就像独立挖矿者一样,然后在共享链上协作挖矿。

P2Pool 是一种混合方法,具有比独立挖矿更加精细化的支付优势,但又不像管理型矿池那样给予矿池操作者太多的控制权。

尽管 P2Pool 减少了矿池运营商的权力集中,但它可能会受到针对共享链本身的 51% 攻击的威胁。P2Pool 的更广泛采用并不解决比特币本身的 51% 攻击问题。相反,P2Pool 使比特币作为一个多样化的挖矿生态系统更加健壮。截至撰写本文时,P2Pool 已经逐渐失去了使用,但新的协议,例如 Stratum v2,可以允许个别矿工选择他们在区块中包含的交易。

哈希攻击

比特币的共识机制理论上容易受到矿工(或矿池)的攻击,他们试图利用自己的算力进行不诚实或破坏性的行为。正如我们所见,共识机制依赖于大多数矿工出于自身利益而诚实行事。然而,如果一个矿工或一组矿工能够获得相当大的算力份额,他们就可以攻击共识机制,破坏比特币网络的安全性和可用性。

需要注意的是,算力攻击对未来共识影响最大。随着时间的推移,最佳区块链上的确认交易变得越来越不可变。虽然理论上,可以在任何深度达到分叉,但在实践中,迫使一个非常深的分叉所需的计算能力是巨大的,使得旧区块变得非常难以更改。算力攻击也不会影响私钥和签名算法的安全性。

共识机制面临的一种攻击场景被称为多数攻击或 51% 攻击。在这种情况下,一组矿工,控制了总网络算力的大部分(例如 51%),串通一气攻击比特币。拥有挖掘大多数区块的能力,攻击者可以导致区块链中故意的“分叉”,双重支付交易,或对特定交易或地址执行拒绝服务攻击。分叉/双花攻击是指攻击者通过在之前的确认区块下方分叉并重新汇聚到备用链上,导致先前确认的区块无效。拥有足够的算力,攻击者可以使六个或更多的区块无效,导致原本被视为不可变的交易(六次确认)被取消。需要注意的是,双花只能对攻击者自己的交易进行,因为攻击者可以生成有效的签名。如果使一笔交易无效使攻击者可以获得不可逆的交易支付或产品而无需付款,那么双花自己的交易可能是有利可图的。

让我们来看一个 51% 攻击的实际例子。在第一章中,我们看到了 Alice 和 Bob 之间的一笔交易。作为卖家,Bob 愿意在不等待确认(在区块中挖矿)的情况下接受付款,因为相比较迅速的客户服务而言,小额商品的双花风险较低。这类似于一些咖啡店接受小于 25 美元的信用卡支付,无需签名,因为信用卡追回的风险较低,而为了获取签名而延迟交易的成本相对较高。相比之下,以比特币出售更昂贵的商品存在双花攻击的风险,即买方广播一笔竞争交易,使用相同的输入(UTXOs),从而取消向商家的支付。51% 攻击允许攻击者在新链中双花自己的交易,从而撤销旧链中相应的交易。

在我们的例子中,恶意攻击者 Mallory 前往 Carol 的画廊,购买了一套描绘 Satoshi Nakamoto 为普罗米修斯的精美画作。Carol 以 25 万美元的比特币将画作出售给了 Mallory。Carol 在仅一次确认后,便将画作包装交给了 Mallory。Mallory 与一个名为 Paul 的共犯合作,Paul 经营着一个庞大的矿池,共犯一旦 Mallory 的交易被纳入一个区块中,就会发起攻击。Paul 指示矿池重新挖掘与包含 Mallory 交易的区块相同高度的区块,用一笔双花交易取代 Mallory 向 Carol 的支付。双花交易消耗相同的 UTXO 并将其支付给 Mallory 的钱包,而不是支付给 Carol,本质上允许 Mallory 保留比特币。然后,Paul 指示矿池挖掘额外的区块,使包含双花交易的链比原始链更长(导致在包含 Mallory 交易的区块下面出现分叉)。当区块链分叉有利于新(更长)链时,双花交易将取代对 Carol 的原始支付。Carol 现在失去了三幅画,并且没有收到支付。在所有这些活动中,Paul 的矿池参与者可能对双花尝试毫无察觉,因为他们使用自动化矿工进行挖矿,无法监控每一笔交易或区块。

为了防范这种攻击,销售价值较高的商品的商家必须在将产品交付给买家之前等待至少六次确认。等待超过六次确认有时可能是有必要的。或者,商家应该使用托管的多签名账户,在资金到位后再等待几次确认。确认次数越多,通过重新组织区块链来使交易无效的难度就越大。对于价值较高的商品,即使买家必须等待 24 小时才能交付,比特币支付仍然会很方便和高效,这大约对应着 144 次确认。

除了双花攻击之外,共识攻击的另一个场景是拒绝为特定参与者(特定比特币地址)提供服务。拥有大多数挖矿算力的攻击者可以审查交易。如果它们被包含在另一个矿工挖掘的区块中,攻击者可以故意分叉并重新挖掘该区块,再次排除特定的交易。这种类型的攻击可能会导致针对特定地址或一组地址的持续拒绝服务,只要攻击者控制了大多数挖矿算力。

尽管其名称是 51% 攻击,但实际上并不需要 51% 的哈希算力。事实上,这样的攻击可以尝试用更小的哈希算力。51% 的阈值只是这种攻击几乎肯定会成功的水平。哈希算力攻击本质上是下一个区块的拉锯战,而“更强大”的一方更有可能获胜。哈希算力较少时,成功的可能性会降低,因为其他矿工通过他们的“诚实”挖矿算力生成了一些区块。从一个角度来看,攻击者拥有的哈希算力越多,他可以故意创建的分叉越长,他可以使过去更多的区块无效,或者他可以控制更多未来的区块。安全研究团队使用统计建模声称,各种类型的哈希算力攻击可能只需要 30% 的哈希算力就可以实现。

由于挖矿池引入的控制中心化,导致了挖矿池运营商进行以盈利为目的的攻击的风险。托管挖矿池中的池运营商控制候选区块的构建,并控制包含哪些交易。这使得挖矿池运营商有权排除某些交易或引入双花交易。如果这种权力滥用以一种有限且微妙的方式进行,挖矿池运营商理论上可以从哈希算力攻击中获利而不被注意到。

然而,并非所有攻击者都会受到利润的驱使。一个潜在的攻击场景是,攻击者打算在没有从这种破坏中获利的情况下破坏比特币网络。对比特币进行恶意攻击可能需要巨大的投资和隐蔽的规划,但可能会由一个资金充裕的、最有可能是由国家赞助的攻击者发起。另外,一个资金充裕的攻击者可以通过同时积累挖矿硬件、妥协挖矿池运营商,并对其他挖矿池进行拒绝服务攻击来攻击比特币。所有这些场景在理论上都是可能的。

毫无疑问,严重的哈希算力攻击会在短期内侵蚀人们对比特币的信心,可能导致价格大幅下跌。然而,比特币网络和软件不断发展,因此比特币社区将采取措施应对攻击。

改变共识规则

共识规则确定了交易和区块的有效性。这些规则是所有比特币节点之间合作的基础,并负责将所有本地视角汇聚成整个网络中的一个一致的区块链。

虽然共识规则在短期内是不变的,并且必须在所有节点之间保持一致,但在长期内它们并不是不变的。为了使比特币系统能够发展和演进,规则可以不时地发生变化,以适应新功能、改进或错误修复。然而,与传统软件开发不同,共识系统的升级要困难得多,并且需要所有参与者之间的协调。

硬分叉

在第282页的“组装和选择区块链”的部分,我们看到比特币网络可能会短暂地分歧,网络的两个部分会在短时间内跟随区块链的两个不同分支。我们看到了这个过程是如何自然地发生的,作为网络正常运行的一部分,以及在挖出一个或多个区块后,网络如何汇聚到一个共同的区块链上。

还有另一种情况会导致网络分歧并跟随两个链的情况:共识规则的变化。这种类型的分叉被称为硬分叉,因为在分叉之后,网络可能无法汇聚到一个单一的链上。相反,这两个链可以独立发展。硬分叉发生在网络的一部分根据与其余网络不同的一组共识规则运行时。这可能是因为存在错误,也可能是因为故意改变了共识规则的实现方式。

硬分叉可以用来改变共识规则,但它们需要系统中所有参与者之间的协调。任何没有升级到新共识规则的节点都无法参与共识机制,并在硬分叉时被迫进入另一个链。因此,硬分叉引入的变化可以被认为是不“向前兼容”的,因为未升级的系统由于新的共识规则而无法处理区块。

让我们通过一个具体的例子来看一下硬分叉的机制。

图12-2显示了一个具有两个分叉的区块链。在第4个区块高度处发生了一个区块的分叉。这是我们在第282页“组装和选择区块链”中看到的一种自发性分叉。随着第5个区块的挖掘,网络汇聚到一个链上,分叉被解决了。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/12.2.png" alt=""><figcaption><p>图 12-2. 一个带有分叉的区块链</p></figcaption></figure>

然而,到了区块高度 6,一个新版本的客户端实现发布了,其中包含了对共识规则的更改。从区块高度 7 开始,运行此新版本实现的矿工将接受一种新类型的比特币;我们将其称为“foocoin”。就在这之后,运行新实现的节点创建了一个包含 foocoin 的交易,而一个使用更新软件的矿工挖出了包含此交易的 7b 区块。

任何未升级软件以验证 foocoin 的节点或矿工现在都无法处理 7b 区块。从他们的角度来看,既包含 foocoin 的交易,也包含该交易的 7b 区块都是无效的,因为他们是基于旧的共识规则进行评估的。这些节点将拒绝该交易和该区块,并且不会传播它们。使用旧规则的任何矿工都不会接受区块 7b,并将继续挖掘一个父块为区块 6 的候选区块。实际上,如果他们所连接的所有节点也遵循旧规则并因此不传播该区块,他们甚至可能都收不到区块 7b。最终,他们将能够挖掘区块 7a,这个区块在旧规则下是有效的,且不包含任何 foocoin 交易。

从这一点开始,两条链将继续分歧。在“b”链上的矿工将继续接受和挖掘包含 foocoin 的交易,而在“a”链上的矿工将继续忽略这些交易。即使区块 8b 不包含任何 foocoin 交易,使用“a”链的矿工也无法处理它。对他们来说,它似乎是一个无效的区块,因为其父区块“7b”未被识别为有效区块。

硬分叉:软件、网络、挖矿和链

对于软件开发者来说,“分叉”一词还有另一种含义,这给“硬分叉”一词增添了困惑。在开源软件中,当一组开发者选择遵循不同的软件路线图并开始竞争性地实现一个开源项目时,就会发生分叉。我们已经讨论了导致硬分叉的两种情况:共识规则中的错误和对共识规则的有意修改。在有意修改共识规则的情况下,软件分叉将先于硬分叉发生。然而,要发生这种类型的硬分叉,必须开发、采纳和启动一种新的共识规则的软件实现。

试图改变共识规则的软件分叉的例子包括比特币 XT 和比特币 Classic。然而,这两个程序都没有导致硬分叉。虽然软件分叉是一个必要的先决条件,但它本身并不足以导致硬分叉发生。要发生硬分叉,必须采纳竞争性的实现并激活新的规则,由矿工、钱包和中间节点完成。相反,比特币核心有许多替代实现,甚至软件分叉,它们不改变共识规则,除非存在错误,否则可以在网络上共存并互操作而不会导致硬分叉。

共识规则在验证交易或区块时可能以明显和明确的方式存在差异。这些规则也可能以更微妙的方式存在差异,比如在应用于比特币脚本或数字签名等密码原语的共识规则的实现上。最后,由于系统限制或实现细节所施加的隐含共识约束,共识规则可能以意想不到的方式存在差异。一个例子是在将比特币核心 0.7 升级到 0.8 时发生的未预期的硬分叉,这是由于用于存储区块的 Berkeley DB 实现的限制引起的。

在概念上,我们可以将硬分叉分为四个阶段:软件分叉、网络分叉、挖矿分叉和链分叉。这个过程始于开发者创建了一个具有修改后共识规则的替代客户端的实现。

当这个分叉实现在网络中部署时,一定比例的矿工、钱包用户和中间节点可能会采用并运行这个实现。首先,网络将会发生分叉。基于原始共识规则实现的节点将拒绝任何根据新规则创建的交易和区块。此外,遵循原始共识规则的节点可能会与向它们发送这些无效交易和区块的任何节点断开连接。因此,网络可能会分裂为两个部分:旧节点只会保持与旧节点连接,而新节点只会与新节点连接。一个基于新规则的区块会在网络中传播并导致网络分裂为两个。新矿工可能会在新区块的基础上挖矿,而旧矿工将在旧规则的基础上挖掘另一条链。由于连接到两个独立网络,分区的网络将使得根据不同共识规则运作的矿工不太可能接收到对方的区块。

矿工和难度的分歧

随着矿工分散到挖掘两条不同的链上,算力被分配到了这两条链之间。挖矿算力可以以任意比例分配到这两条链上。新规则可能只被少数矿工遵循,也可能被绝大多数矿工遵循。

举个例子,假设出现了80% - 20%的分割,其中大部分的挖矿算力采用了新的共识规则。同时假设分叉发生在一个重新调整难度期之后。

这两条链将分别继承重新调整难度期的难度。新的共识规则将有80%的之前可用的挖矿算力加入其中。从这条链的角度来看,挖矿算力突然下降了20%,相对于之前的周期。区块平均每12.5分钟被发现一次,代表了用于扩展这条链的挖矿算力下降了20%。这种区块发行速率会持续(除非挖矿算力发生变化),直到挖出2,016个区块,这将需要大约25,200分钟(每个区块12.5分钟),或者17.5天。之后,将进行一次重新调整,并根据这条链中挖矿算力的减少(减少了20%)来产生10分钟的区块。

少数链,使用旧规则挖矿,只拥有20%的挖矿算力,将面临更为困难的任务。在这条链上,区块的平均挖掘时间将增加到50分钟。在挖掘2,016个区块之前,难度将不会进行调整,这将需要100,800分钟,或者大约10周来挖掘完毕。假设每个区块的容量是固定的,这也将导致交易容量减少5倍,因为每小时可用于记录交易的区块数量减少了。

有争议的硬分叉

这标志着去中心化共识软件开发的黎明。就像开发中的其他创新改变了软件的方法和产品,并在其后产生了新的方法、新的工具和新的社区一样,共识软件开发也代表着计算机科学的一个新领域。通过比特币开发的辩论、实验和磨难,我们将看到新的开发工具、实践、方法和社区涌现出来。

硬分叉被视为风险,因为它迫使少数人要么升级,要么留在少数链上。许多人认为将整个系统分裂为两个竞争系统的风险是不可接受的。因此,许多开发人员不愿意使用硬分叉机制来实现对共识规则的升级,除非整个网络几乎一致支持。任何没有几乎一致支持的硬分叉提案都被认为是太有争议的,不值得尝试,因为会有分裂系统的风险。

我们已经看到新的方法论出现来解决硬分叉的风险。在接下来的部分中,我们将看看软分叉以及共识修改的信号和激活方法

软分叉

并非所有的共识规则更改都会引起硬分叉。只有那些不向前兼容的共识更改才会导致分叉。如果更改是以这样一种方式实现的,即未修改的客户端仍然将交易或区块视为在先前规则下有效,那么更改就可以在不进行分叉的情况下进行。

术语软分叉被引入以区分这种升级方法与“硬分叉”。实际上,软分叉根本不是一种分叉。软分叉是对共识规则的前向兼容更改,使得未升级的客户端可以继续与新规则保持一致。

软分叉的一个并非立即显而易见的方面是,软分叉升级只能用于约束共识规则,而不能用于扩展它们。为了实现向前兼容,根据新规则创建的交易和区块必须在旧规则下也是有效的,但反之则不然。新规则只能限制有效内容;否则,它们将在旧规则下被拒绝时触发硬分叉。

软分叉可以以多种方式实现——这个术语并未指定特定的方法,而是一组具有共同特点的方法:它们不要求所有节点都进行升级,也不会强制非升级节点退出共识。比特币已经实施了两个软分叉,基于对NOP操作码的重新解释。比特币脚本中保留了10个NOP操作码以备将来使用,从NOP1到NOP10。根据共识规则,脚本中存在这些操作码被解释为一个空操作符,意味着它们没有任何效果。执行在NOP操作码之后继续,就好像它不存在一样。

因此,软分叉可以修改NOP代码的语义,赋予它新的含义。例如,BIP65(CHECKLOCKTIMEVERIFY)重新解释了NOP2操作码。实现BIP65的客户端将NOP2解释为OP_CHECKLOCKTIMEVERIFY,并对包含此操作码的UTXO施加绝对锁定时间的共识规则。这个更改是一个软分叉,因为根据BIP65有效的交易也在不实现(不了解)BIP65的任何客户端上有效。对于旧的客户端来说,脚本包含一个NOP代码,而这个代码会被忽略。

软分叉的批评

基于 NOP 操作码的软分叉相对来说是比较少有争议的。NOP 操作码被放置在比特币脚本中,其明确的目标是允许非破坏性的升级。

然而,许多开发者担心软分叉升级的其他方法会做出不可接受的折衷。软分叉变更的常见批评包括:

  1. 技术债务:由于软分叉比硬分叉升级更加技术复杂,它们引入了技术债务。技术债务是指由于过去的设计折衷而增加了未来代码维护成本的情况。代码复杂性又增加了出现错误和安全漏洞的可能性。
  2. 验证放宽:未修改的客户端将交易视为有效,而不评估修改后的共识规则。实际上,未修改的客户端不是使用完整范围的共识规则进行验证,因为它们对新规则视而不见。这适用于基于 NOP 的升级,以及其他软分叉升级。
  3. 不可逆转的升级:由于软分叉创建了具有额外共识约束的交易,它们在实践中变成了不可逆转的升级。如果软分叉升级在激活后被撤销,那么根据旧规则创建的任何交易都可能导致在旧规则下损失资金。例如,如果 CLTV 交易在旧规则下进行评估,则没有时间锁定约束,可以随时使用。因此,批评者认为,由于错误而必须撤销的软分叉几乎肯定会导致资金损失。

基于区块版本的软分叉信号

由于软分叉允许未修改的客户端继续在共识中运行,一种“激活”软分叉的机制是通过矿工表明他们已准备好并愿意执行新的共识规则。如果所有矿工都执行新规则,那么未升级的节点接受的块不会被升级的节点拒绝,因此不会有风险。这种机制由 BIP34 引入。

BIP34: 信号传递和激活

BIP34使用区块版本字段允许矿工表明他们对特定共识规则变更的准备就绪。在BIP34之前,按照共识规定,区块版本被设置为“1”,但并未被强制执行。

BIP34定义了一项共识规则变更,要求coinbase交易的coinbase字段(输入)包含区块高度。在BIP34之前,coinbase可以包含矿工选择包含的任意数据。激活BIP34后,有效的区块必须在coinbase的开头包含特定的区块高度,并且用大于或等于“2”的区块版本号进行标识。

为了表示他们准备好执行BIP34规则,矿工将区块版本设置为“2”,而不是“1”。这并不立即使版本“1”的区块无效。一旦激活,版本“1”的区块将变得无效,所有版本“2”的区块必须包含coinbase中的区块高度才能被视为有效。

BIP34定义了一个基于最近1,000个区块的滚动窗口的两步激活机制。矿工通过构造区块并将版本号设置为“2”来表示他们个人对BIP34的准备就绪。严格来说,这些区块还不必符合在coinbase交易中包含区块高度的新共识规则,因为共识规则尚未被激活。共识规则分两步激活:

  • 如果最近1,000个区块中有75%(即750个)标记为版本“2”,则版本“2”区块必须在coinbase交易中包含区块高度,否则将被拒绝为无效。版本“1”区块仍然被网络接受,不需要包含区块高度。在此期间,新旧共识规则并存。
  • 当95%(即最近1,000个区块中的950个)为版本“2”时,版本“1”区块不再被视为有效。只有包含coinbase中区块高度的版本“2”区块才有效(根据以前的阈值)。此后,所有区块必须符合新的共识规则,所有有效的区块必须在coinbase交易中包含区块高度。

在BIP34规则下成功进行信号传递和激活后,这一机制又被用于两次激活软分叉:

  • BIP66 严格的DER签名编码通过使用区块版本“3”进行BIP34风格的信号传递和激活。
  • BIP65 CHECKLOCKTIMEVERIFY通过使用区块版本“4”进行BIP34风格的信号传递和激活。

在激活BIP65之后,BIP34的信号传递和激活机制被废除,并被下文描述的BIP9信号传递机制所取代。

BIP9: 信号传递和激活

BIP34、BIP66和BIP65采用的机制成功激活了三次软分叉。然而,它被替换掉,因为它有几个局限性:

  • 通过使用区块版本的整数值,一次只能激活一个软分叉,因此需要在软分叉提案之间进行协调,并就它们的优先级和顺序达成一致。
  • 此外,由于区块版本是递增的,这种机制没有提供拒绝更改然后提出不同更改的简单方法。如果旧客户端仍在运行,它们可能会将新更改的信号误认为是先前被拒绝更改的信号。
  • 每次新更改都会无法撤销地减少未来更改的可用区块版本。

为了克服这些挑战并提高未来更改的实施速度和便利性,提出了BIP9。

BIP9将区块版本解释为位字段而不是整数。由于区块版本最初是用作整数表示的,从版本1到版本4,因此只剩下29位可用于作为位字段。这意味着有29个位可用于独立且同时地对29个不同的提案进行准备就绪的信号传递。

BIP9还设置了信号传递和激活的最大时间。这样,矿工不需要永远进行信号传递。如果在超时期限(在提案中定义)内未激活提案,则将视为被拒绝。提案可以使用不同的位重新提交进行信号传递,以延长激活期限。

此外,在超时期限过去并且功能已被激活或拒绝后,信号位可以在不引起混淆的情况下用于其他功能。因此,最多可以同时传递29个更改。超时后,位可以“回收”以提出新的更改。

注意:虽然信号位可以被重用或回收,只要投票期不重叠,但BIP9的作者建议只在必要时重用位;由于旧软件中的错误可能导致意外行为。简而言之,在所有29个位都被使用一次之前,我们不应该期望看到重用。

提出的更改由包含以下字段的数据结构标识:

名称

用于区分提案之间的简短描述。通常是BIP描述提案,格式为“bipN”,其中N是BIP编号。

比特

0到28,矿工用于表示对此提案的批准意见的区块版本中的比特。

开始时间

信号开始的时间(基于MTP),在此之后,比特的值被解释为对提案的准备就绪进行信号传递。

结束时间

如果在这个时间(基于MTP)之后,变更尚未达到激活阈值,那么该变更将被视为被拒绝。

与BIP34不同,BIP9根据2,016个区块的难度调整周期中的整个时间间隔来计算激活信号。对于每个调整周期,如果对某个提案进行信号传递的区块总数超过95%(即2,016个中的1,916个),则该提案将在下一个调整周期后被激活。

BIP9提供了一个提案状态图,以图12-3所示的各种阶段和转换来说明提案的不同阶段和转换。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/12.3.png" alt=""><figcaption><p>图 12-3. BIP9 状态转移图</p></figcaption></figure>

提案一旦在比特币软件中的参数已知(已定义),则会进入DEFINED状态。对于MTP在开始时间之后的区块,提案状态将转换为STARTED。如果在一个调整周期内超过了投票门槛,并且超时时间尚未到期,则提案状态将转换为LOCKED_IN。一个调整周期后,提案将变为ACTIVE。一旦提案达到该状态,将永久保持在ACTIVE状态。如果超时时间到期,而投票门槛尚未达到,则提案状态将变为FAILED,表示提案被拒绝。FAILED提案将永久保持在该状态。

BIP9首次用于激活CHECKSEQUENCEVERIFY及相关的BIPs(68、112、113)。名为“csv”的提案于2016年7月成功激活。

该标准在BIP9(带有超时和延迟的版本位)中定义。

BIP8: 强制性的提前激活期锁定

在BIP9成功用于与CSV相关的软分叉之后,下一个软分叉共识更改的实施也试图使用它进行矿工强制激活。然而,一些人反对该软分叉提案,称为segwit,很少有矿工在几个月内表明愿意执行segwit。

后来发现,一些矿工,尤其是与持不同意见者有关的矿工,可能使用了一种称为隐蔽ASICBoost的功能的硬件,使他们比使用隐蔽ASICBoost的其他矿工具有隐藏优势。无意中,segwit干扰了使用隐蔽ASICBoost的能力 - 如果激活segwit,那些使用它的矿工将失去他们的隐藏优势。

在社区发现这种利益冲突后,一些用户决定行使他们的权力,不接受矿工不遵循特定规则的区块。用户最终想要的规则是segwit添加的新规则,但用户想要通过利用计划执行segwit规则的大量节点来增加他们的努力,只要有足够的矿工表示愿意。一个化名开发者提出了BIP148,该提案要求实施它的任何节点从某个日期开始拒绝所有不为segwit进行信号的区块,并持续到segwit激活。

尽管只有少数用户实际运行了BIP148代码,但许多其他用户似乎同意这种情绪,并可能准备承诺BIP148。在BIP148即将生效几天前,几乎所有矿工开始表明他们准备好执行segwit规则。大约两周后,Segwit达到了其锁定阈值,并在两周后激活。

许多用户开始相信,BIP9的一个缺陷是矿工可以通过不发出信号一年来阻止激活尝试成功。他们希望有一种机制,可以确保软分叉在特定的区块高度激活,但也允许矿工提前发出信号以锁定它。

为此开发的方法是BIP8,它与BIP9类似,不同之处在于它定义了一个MUST_SIGNAL期间,在这个期间,矿工必须发出信号表示他们准备执行软分叉提案。

在2021年,已经发布了使用BIP8尝试激活taproot提案的软件,并且有证据表明至少有少数用户运行了该软件。其中一些用户还声称,他们愿意使用BIP8来强制矿工激活taproot是它最终激活的原因。他们声称,如果taproot没有迅速激活,其他用户也会开始运行BIP8。不幸的是,我们无法证明会发生什么,因此我们无法确定BIP8对taproot激活的贡献有多大。

速审试验(Speedy trial): 快速失败或最终成功

尽管 BIP9 本身似乎没有导致 SegWit 的激活,尽管该提案得到了广泛支持,但对许多协议开发人员来说,BIP9 本身是否是失败并不清楚。正如前面提到的,矿工最初未能表达对 SegWit 的支持可能主要是由于一次性利益冲突导致的,这种情况在未来可能不再适用。对一些人来说,值得再次尝试 BIP9。另一些人则持不同意见,希望使用 BIP8。

经过数月的讨论,那些对特定激活方案最感兴趣的人之间达成了一个妥协,以激活 Taproot。提出了修改版的 BIP9,该版将只允许矿工在非常短的时间内表达他们打算执行 Taproot 规则的意图。如果信号不成功,可以使用其他激活机制(或者可能放弃这个想法)。如果信号成功,将在约六个月后的特定区块高度开始执行。这个机制由其中一位促进者命名为 “speedy trial”。

Speedy trial 激活尝试进行了,矿工迅速表示愿意执行 Taproot 规则,大约六个月后成功激活了 Taproot。对于 speedy trial 的支持者来说,这是一个明显的成功。其他人仍然对未使用 BIP8 表示失望。

目前尚不清楚 speedy trial 是否会再次用于未来尝试激活软分叉。

共识软件开发

共识软件继续演进,人们就改变共识规则的各种机制进行广泛讨论。由于比特币本身的特性,对于改变而言,它设定了极高的协调和共识标准。作为一个去中心化系统,它没有可以对网络参与者强加意愿的“权威”。权力分散在多个利益相关者之间,如矿工、协议开发者、钱包开发者、交易所、商家和最终用户。决策不能由这些利益相关者之一单方面做出。例如,虽然矿工可以通过简单多数(51%)对交易进行审查,但他们受到其他利益相关者的同意的限制。如果他们单方面行动,其他参与者可能会拒绝接受他们的区块,使经济活动继续在少数链上进行。没有经济活动(交易、商家、钱包、交易所),矿工将挖掘一种价值为空的货币,生成空块。权力的分散意味着所有参与者必须协调一致,否则就无法进行任何改变。在这个系统中,稳态是稳定的状态,只有在有非常大多数人的强烈共识下才可能进行一些少数的改变。软分叉的95%阈值反映了这一现实。

重要的是要认识到,对于共识开发来说,没有完美的解决方案。硬分叉和软分叉都涉及权衡。对于某些类型的改变,软分叉可能是更好的选择;对于其他类型的改变,硬分叉可能更合适。没有完美的选择;两者都带有风险。共识软件开发的一个不变特征是改变是困难的,共识迫使进行妥协。

一些人认为这是共识系统的弱点。随着时间的推移,你可能会认识到这是系统的最大优势。

在本书的这一部分,我们已经完成了对比特币系统本身的讨论。剩下的是建立在比特币之上的软件、工具和其他协议。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论