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

  • berry
  • 发布于 2025-02-09 19:35
  • 阅读 11

综合介绍

“挖矿”这个词有点误导性。通过唤起对贵金属开采的联想,它将我们的注意力集中在挖矿的奖励上,即每个区块中生成的新比特币。尽管挖矿是通过这种奖励来激励的,但挖矿的主要目的并不是奖励或生成新比特币。如果你将挖矿仅视为生成比特币的过程,那么你误把手段(激励)当作了过程的目标。挖矿是支撑去中心化清算所的机制,用于验证和清算交易。挖矿是使比特币特殊的发明之一,是基于P2P数字现金的去中心化共识机制的基础之一。

挖矿保障了比特币系统的安全性,并使得网络范围内的共识得以出现,而无需中央权威。新铸造的比特币和交易费用的奖励是一种激励机制,将矿工的行为与网络的安全性对齐,同时实施货币供应。

挖矿是比特币的共识安全性分散化的机制之一。

矿工将新交易记录在全球区块链上。每隔约10分钟就会挖出一个新的区块,其中包含自上一个区块以来发生的交易,从而将这些交易添加到区块链中。成为区块一部分并添加到区块链上的交易被视为已确认,这使得比特币的新所有者知道,他们收到的比特币已经受到了不可撤销的保障。

此外,区块链中的交易具有由它们在区块链中的位置定义的拓扑顺序。如果一个交易出现在较早的区块中,或者在同一个区块中较早出现,那么它就比另一个交易更早。在比特币协议中,仅当交易花费了出现在区块链中较早的交易的输出时(无论它们是在同一个区块中还是在较早的区块中),该交易才有效,且仅当没有任何先前的交易花费了这些相同的输出时才有效。在一条区块链中,拓扑顺序的执行确保没有两个有效的交易可以花费相同的输出,从而消除了双重支付的问题。在一些建立在比特币之上的协议中,比特币交易的拓扑顺序也被用来建立事件序列;我们将在“单次使用密封”一节中进一步讨论这个想法。

矿工因提供挖矿所带来的安全性而获得两种类型的奖励:每个新区块中创建的新比特币(称为补贴),以及包含在区块中的所有交易的交易费用。为了获得这个奖励,矿工们竞争以满足基于加密哈希算法的挑战。解决这个问题的解决方案,称为工作证明,包含在新区块中,并作为证明矿工投入了大量的计算工作量。解决工作证明算法以获得奖励和记录交易到区块链的权利的竞争,是比特币安全模型的基础。

比特币的货币供应是通过类似于中央银行通过印刷纸币发行新货币的过程创建的。矿工每增加一个区块就可以添加的新比特币的最大数量大约每四年减少一半(或者确切地说是每增加210,000个区块)。它从2009年1月的每个区块50个比特币开始,于2012年11月减半为每个区块25个比特币。在2016年7月再次减半为每个区块12.5个比特币,然后在2020年5月减半为6.25个比特币。根据这个公式,挖矿奖励将以指数方式递减,直到大约在2140年,所有的比特币都将被发行完毕。在2140年之后,将不会再发行新的比特币。

比特币矿工还从交易中获得费用。每个交易都可能包含一个交易费,形式为交易的输入和输出之间的比特币的余额。赢得比特币的矿工可以“留下找零”来处理包含在获胜区块中的交易。如今,交易费通常仅占矿工收入的一小部分,绝大部分收入来自新铸造的比特币。然而,随着奖励随时间减少和每个区块中的交易数量增加,越来越大比例的挖矿收入将来自交易费。逐渐地,挖矿奖励将被交易费主导,这将成为矿工的主要激励。在2140年之后,每个区块中的新比特币数量将降至零,挖矿将仅由交易费来激励。

在本章中,我们首先将挖矿作为货币供应机制进行讨论,然后再看看挖矿的最重要功能:支撑比特币安全的去中心化共识机制。

为了理解挖矿和共识,我们将追踪Alice的交易,看它是如何被Jing的挖矿设备接收并添加到一个区块中的。然后,我们将跟踪这个区块是如何被挖出、添加到区块链中,并通过紧急共识的过程被比特币网络接受的。

比特币经济学与货币创造

比特币在每个区块创建时以固定且递减的速率铸造。每个区块平均每10分钟产生一次,其中包含全新的比特币,从无到有地创造出来。每经过210,000个区块,或大约每四年,货币发行速度就会减少50%。在网络运行的头四年中,每个区块包含50个新的比特币。

第一次减半发生在第210,000个区块。在本书出版后的下一次减半预期将在第840,000个区块发生,这可能会在2024年4月或5月产生。新比特币的发行速度经过32次这样的减半呈指数递减,直到第6,720,000个区块(大约在2137年开采),当时它将达到最小的货币单位1个聪。最终,在大约2140年之后,几乎将发行20,999,999,997,690,000个聪,或几乎21百万比特币。此后,区块将不再包含新的比特币,矿工将仅通过交易费获得奖励。图12-1显示了随着货币发行减少,随时间推移在流通中的总比特币数量。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/12.1.png" alt=""><figcaption><p>图 12-1. 随时间推移,比特币货币供应根据几何递减的发行率而变化</p></figcaption></figure>

注意:比特币的最大挖掘数量是比特币可能的挖掘奖励的上限。实际上,矿工可能会有意地挖出一个奖励不足的区块。这样的区块已经被挖出来了,未来可能还会被挖出更多,导致货币的总发行量降低。

在示例 12-1 中的代码中,我们计算将发行的比特币总量。

示例 12-1. 用于计算总比特币发行量的脚本

# 定义常量
initial_reward = 50  # 每个区块的初始奖励,单位为比特币
halving_interval = 210000  # 每次减半之间的区块数量
total_blocks = 6930000  # 预计直到最后一次减半的总区块数量

# 计算总的比特币发行量
total_bitcoin_issued = 0
reward = initial_reward
halvings = 0

# 循环遍历减半次数,直到达到最后一次减半
while halvings &lt; total_blocks // halving_interval:
    total_bitcoin_issued += halving_interval * reward
    reward /= 2  # 将奖励减半
    halvings += 1

# 计算最后一次减半后剩余区块的发行量
remaining_blocks = total_blocks % halving_interval
total_bitcoin_issued += remaining_blocks * reward

# 输出总的比特币发行量
print("总比特币发行量:", total_bitcoin_issued, "比特币")

示例 12-2 展示了运行该脚本所产生的输出,所以比特币发行总量大概为2100万个。

示例 12-2. 运行 max_money.py 脚本

$ python max_money.py
总比特币发行量: 20999999.99755528 比特币

有限且递减的发行量创造了一个固定的货币供应,抵制了通货膨胀。与中央银行可以无限制地印刷法定货币不同,没有任何个人或实体有能力膨胀比特币的供应。

通货紧缩的货币

固定且递减的货币发行最重要且备受争议的后果是货币往往具有固有的通货紧缩性。通货紧缩是由于供需不平衡导致货币价值上升(和汇率上升)的现象。价格通货紧缩是通货膨胀的反面,意味着货币随时间具有更多的购买力。

许多经济学家认为通货紧缩的经济是一场灾难,应该尽一切努力避免。这是因为在通货紧缩迅速的时期,人们往往会囤积货币而不是花费它,希望价格会下跌。这种现象在日本的“失落的十年”期间出现过,当时需求完全崩溃,推动该货币陷入通货紧缩螺旋。

比特币专家认为,通货紧缩本身并不是坏事。相反,通货紧缩与需求崩溃相关,因为这是我们研究通货紧缩的最明显的例子。在可能无限印刷货币的法定货币中,要进入通货紧缩螺旋是非常困难的,除非需求完全崩溃且不愿意印刷货币。比特币中的通货紧缩不是由于需求崩溃引起的,而是由于可预测的供应受限造成的。 当然,通货紧缩的积极方面是它是通货膨胀的反面。通货膨胀会导致货币逐渐贬值,从而导致一种形式的隐性税收,惩罚储户以援助债务人(包括最大的债务人,即政府本身)。受政府控制的货币存在易于发行债务的道德风险,这些债务后来可以通过贬值以牺牲储户的利益而被抹去。

尚待观察的是,当通货紧缩不是由经济迅速收缩驱动时,货币的通货紧缩特征是否是一个问题,还是一个优势,因为对通货膨胀和贬值的保护超过了通货紧缩的风险。

去中心化共识

在前一章中,我们讨论了区块链,即所有交易的全局列表,每个比特币网络中的参与者都接受它作为所有权转移的权威记录。

但是,如何让网络中的每个人在不必信任任何人的情况下就谁拥有什么达成单一的普遍“真相”呢?所有传统的支付系统都依赖于一个信任模型,其中有一个中央机构提供清算服务,基本上验证和清算所有交易。比特币没有中央机构,但一些全节点却拥有一个完整的公共区块链副本,可以将其视为权威记录,而无需信任任何人。区块链不是由中央机构创建的,而是由网络中的每个节点独立组装而成的。不知何故,网络中的每个节点,在通过不安全的网络连接传输的信息的作用下,都能得出相同的结论,并组装出与其他人相同的区块链副本。本章将探讨比特币网络在没有中央机构的情况下如何实现全局共识的过程。

中本聪的一个发明是分布式的紧急共识机制。紧急共识是指共识不是明确地实现的——没有选举或固定的共识达成时刻。相反,共识是成千上万个独立节点异步交互的结果,所有节点都遵循简单的规则。比特币的所有属性,包括货币、交易、支付以及不依赖中央机构或信任的安全模型,都源于这一发明。

比特币的分散式共识是由网络中各个节点上独立进行的四个过程相互作用而产生的:

  • 每个完整节点对每个交易进行独立验证,基于一套全面的标准清单
  • 挖矿节点将这些交易独立聚合到新的区块中,并通过工作证明算法进行计算证明
  • 每个节点对新区块进行独立验证,并将其组装成链
  • 每个节点通过工作证明选择具有最大累积计算量的链

在接下来的几节中,我们将探讨这些过程以及它们如何相互作用,以创造出网络范围共识的 emergent 性质,使得任何比特币节点都能组装出自己的权威、可信的、公共的、全球区块链副本。

交易的独立验证

在第6章中,我们看到钱包软件通过收集UTXO(未使用的交易输出)、提供适当的身份验证数据,然后构建分配给新所有者的新输出来创建交易。然后,生成的交易被发送到比特币网络中的相邻节点,以便在整个比特币网络中传播。

然而,在将交易转发到其邻居之前,每个接收到交易的比特币节点都会首先验证该交易。这确保只有有效的交易才会在网络中传播,而无效的交易则会在首个遇到它们的节点处被丢弃。

每个节点都会根据长长的检查清单验证每个交易(可对比tx_check.cpp的CheckTransaction、validation.cpp来分析):

  • 交易的语法和数据结构必须正确。
  • 输入和输出列表都不为空。
  • 交易的重量足够小,以便使其适合在一个区块中。
  • 每个输出值以及总值必须在允许的值范围内(大于等于零,但不超过2100万比特币)。
  • 锁定时间等于INT_MAX,或者锁定时间和序列值符合锁定时间和BIP68规则。
  • 交易中包含的签名操作(SIGOPS)的数量少于签名操作限制
  • 花费的输出与内存池中的输出或主分支中未花费的输出匹配。
  • 对于每个输入,如果引用的输出交易是coinbase输出,则必须至少有COINBASE_MATURITY(100)次确认。任何绝对或相对锁定时间也必须满足。节点可能在它们成熟之前转发交易到一个区块,因为如果包含在下一个区块中,它们就会成熟。
  • 如果输入值的总和小于输出值的总和,则拒绝。
  • 每个输入的脚本必须针对相应的输出脚本进行验证。

请注意,这些条件随着时间的推移而改变,以添加新功能或解决新类型的拒绝服务攻击。

通过在收到每个交易并在传播之前进行独立验证,每个节点构建了一个有效但尚未确认的交易池,称为内存池或mempool。

tx_check.cpp::CheckTransaction

bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
{
    // 输入和输出列表都不为空。
    // Basic checks that don't depend on any context
    if (tx.vin.empty())
        return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty");
    if (tx.vout.empty())
        return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-empty");
    // 交易的重量足够小,以便使其适合在一个区块中。
    // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
    if (::GetSerializeSize(TX_NO_WITNESS(tx)) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) {
        return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-oversize");
    }

    // 每个输出值以及总值必须在允许的值范围内(大于等于零,但不超过2100万比特币)。
    // Check for negative or overflow output values (see CVE-2010-5139)
    CAmount nValueOut = 0;
    for (const auto& txout : tx.vout)
    {
        if (txout.nValue &lt; 0)
            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-negative");
        if (txout.nValue > MAX_MONEY)
            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-toolarge");
        nValueOut += txout.nValue;
        if (!MoneyRange(nValueOut))
            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-txouttotal-toolarge");
    }

    // Check for duplicate inputs (see CVE-2018-17144)
    // While Consensus::CheckTxInputs does check if all inputs of a tx are available, and UpdateCoins marks all inputs
    // of a tx as spent, it does not check if the tx has duplicate inputs.
    // Failure to run this check will result in either a crash or an inflation bug, depending on the implementation of
    // the underlying coins database.
    std::set&lt;COutPoint> vInOutPoints;
    for (const auto& txin : tx.vin) {
        if (!vInOutPoints.insert(txin.prevout).second)
            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate");
    }

    if (tx.IsCoinBase())
    {
        if (tx.vin[0].scriptSig.size() &lt; 2 || tx.vin[0].scriptSig.size() > 100)
            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cb-length");
    }
    else
    {
        for (const auto& txin : tx.vin)
            if (txin.prevout.IsNull())
                return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-prevout-null");
    }

    return true;
}

validation.cpp::CheckBlock

bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot)
{
    // These are checks that are independent of context.

    if (block.fChecked)
        return true;

    // Check that the header is valid (particularly PoW).  This is mostly
    // redundant with the call in AcceptBlockHeader.
    if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW))
        return false;

    // Signet only: check block solution
    if (consensusParams.signet_blocks && fCheckPOW && !CheckSignetBlockSolution(block, consensusParams)) {
        return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-signet-blksig", "signet block signature validation failure");
    }

    // Check the merkle root.
    if (fCheckMerkleRoot && !CheckMerkleRoot(block, state)) {
        return false;
    }

    // All potential-corruption validation must be done before we do any
    // transaction validation, as otherwise we may mark the header as invalid
    // because we receive the wrong transactions for it.
    // Note that witness malleability is checked in ContextualCheckBlock, so no
    // checks that use witness data may be performed here.

    // Size limits
    if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(TX_NO_WITNESS(block)) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
        return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-length", "size limits failed");

    // First transaction must be coinbase, the rest must not be
    if (block.vtx.empty() || !block.vtx[0]->IsCoinBase())
        return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-missing", "first tx is not coinbase");
    for (unsigned int i = 1; i &lt; block.vtx.size(); i++)
        if (block.vtx[i]->IsCoinBase())
            return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-multiple", "more than one coinbase");

    // Check transactions
    // Must check for duplicate inputs (see CVE-2018-17144)
    for (const auto& tx : block.vtx) {
        TxValidationState tx_state;
        if (!CheckTransaction(*tx, tx_state)) {
            // CheckBlock() does context-free validation checks. The only
            // possible failures are consensus failures.
            assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS);
            return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
                                 strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), tx_state.GetDebugMessage()));
        }
    }
    unsigned int nSigOps = 0;
    for (const auto& tx : block.vtx)
    {
        nSigOps += GetLegacySigOpCount(*tx);
    }
    if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST)
        return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "out-of-bounds SigOpCount");

    if (fCheckPOW && fCheckMerkleRoot)
        block.fChecked = true;

    return true;
}

挖矿节点

一些节点在比特币网络中是专门的节点,称为矿工节点。小静是一个比特币矿工;他通过运行“挖矿设备”来赚取比特币,这是一个专门设计用于挖矿比特币的计算机硬件系统。小静的专用挖矿硬件连接到运行完整节点的服务器。与其他完整节点一样,小静的节点接收并传播比特币网络上的未确认交易。然而,小静的节点还将这些交易聚合到新的区块中。

让我们跟随在Alice从Bob购买商品时(见“从在线商店购买”第16页)创建的区块。为了演示本章的概念,让我们假设包含Alice交易的区块是由小静的挖矿系统挖出的,并跟踪Alice的交易如何成为这个新区块的一部分。

小静的挖矿节点维护着区块链的本地副本。在Alice购买商品时,小静的节点已经跟上了具有最多工作证明的区块链。小静的节点正在监听交易,尝试挖掘新的区块,并且还在监听其他节点发现的区块。当小静的节点正在挖掘时,它通过比特币网络接收到了一个新的区块。这个区块的到来标志着对该区块的搜索结束,以及对下一个区块的搜索开始。

在前几分钟里,小静的节点在寻找上一个区块的解决方案的同时,还在准备下一个区块的交易。到现在为止,它已经在内存池中收集了几千个交易。在接收到新的区块并验证通过后,小静的节点还将把它与内存池中的所有交易进行比较,并删除已经包含在该区块中的交易。留在内存池中的交易是未确认的,正在等待记录在新的区块中。

小静的节点立即构建一个新的部分区块,作为下一个区块的候选区块。这个区块称为候选区块,因为它还不是一个有效的区块,因为它不包含有效的工作证明。只有当矿工成功找到符合工作证明算法的解决方案时,区块才变得有效。

当小静的节点聚合来自内存池的所有交易时,新的候选区块将包含数千个每笔交易都支付的交易费,他将尝试领取这些费用。

Coinbase交易

在任何区块中,第一个交易都是一笔特殊的交易,称为coinbase交易。这笔交易由Jing的节点构建,并支付他挖矿所获得的奖励。

Jing的节点将coinbase交易创建为对自己钱包的支付。Jing收集的挖矿奖励总额是区块补贴(2023年为6.25个新比特币)和包含在区块中的所有交易的交易费用之和。

与常规交易不同,coinbase交易不消耗(花费)UTXO作为输入。相反,它只有一个输入,称为coinbase输入,隐含包含区块奖励。coinbase交易必须至少有一个输出,并且可以有尽可能多的输出,以适应区块。在2023年的coinbase交易中,常见的是有两个输出:其中一个是使用OP_RETURN的零值输出,用于承诺区块中所有隔离见证(segwit)交易的所有见证。另一个输出支付矿工的奖励。

Coinbase奖励和交易费

为构建coinbase交易,Jing的节点首先计算交易费的总额:

<pre><code><strong>Total Fees = Sum(Inputs) − Sum(Outputs) </strong></code></pre>

接下来,Jing的节点计算新区块的正确奖励。奖励是根据区块高度计算的,从每个区块的50比特币开始,每210,000个区块减半一次。

可以在比特币核心客户端中的GetBlockSubsidy函数中看到计算方式,如示例12-3所示。

示例 12-3. 计算区块奖励 —— 函数 GetBlockSubsidy,比特币核心客户端,main.cpp

<pre class="language-cpp"><code class="lang-cpp">CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) { int halvings = nHeight / consensusParams.nSubsidyHalvingInterval; // Force block reward to zero when right shift is undefined. if (halvings >= 64) return 0; CAmount nSubsidy = 50 * COIN; // Subsidy is cut in half every 210,000 blocks. <strong> nSubsidy >>= halvings; </strong> return nSubsidy; } </code></pre>

初始补贴以 satoshi 为单位计算,将 50 乘以 COIN 常量(100,000,000 satoshi)。这将初始奖励(nSubsidy)设置为 50 亿 satoshi。

接下来,该函数通过当前区块高度除以减半间隔(SubsidyHalvingInterval)来计算已发生的减半次数。

然后,函数使用二进制右移操作符,在每次减半中将奖励(nSubsidy)除以二。对于区块 277,316,这将使 50 亿 satoshi 的奖励右移一次(一次减半),结果为 25 亿 satoshi,或 25 个比特币。经过第 33 次减半后,奖励将被舍入为零。使用二进制右移操作符是因为它比多次重复的除法更有效。为了避免潜在的错误,当进行了 63 次减半后,将跳过移位操作,并将补贴设置为 0。

最后,将 coinbase 奖励(nSubsidy)与交易费用(nFees)相加,并返回总和。

注意:如果 Jing 的挖矿节点编写 coinbase 交易,是什么阻止了 Jing “奖励”自己 100 或 1,000 个比特币?答案是,夸大的奖励将导致其他所有人都认为该区块无效,从而浪费了 Jing 用于 PoW 的电力。只有在所有人接受该区块时,Jing 才能花费奖励。

Coinbase 交易的结构

通过这些计算,Jing 的节点随后构建 coinbase 交易,以支付他自己的区块奖励。

coinbase 交易具有特殊的格式。它不是像普通交易输入一样指定要花费的前一个 UTXO,而是有一个“coinbase”输入。我们在“Inputs”(第123页)中研究了交易输入。让我们将普通交易输入与 coinbase 交易输入进行比较。表12-1显示了普通交易的结构,而表12-2显示了coinbase交易输入的结构。

表12-1. “正常”交易输入的结构

大小 字段 描述
32字节 交易哈希(Transaction Hash) 指向要被消费的 UTXO 所在的交易
4字节 输出索引(Output Index) 要被消费的 UTXO 的索引号,第一个是 0
1-9字节(紧凑编码) 脚本长度(Script Size) 脚本长度(以字节为单位),后跟着
可变长度 输入脚本(Input Script) 一个满足UTXO输出脚本条件的脚本
4字节 序列号(Sequence Number) 用于BIP68时间锁定和交易替换信号的多用途字段

表12-2. Coinbase交易输入的结构

大小 字段 描述
32字节 交易哈希(Transaction Hash) 所有位都是零:不是交易哈希引用
4字节 输出索引(Output Index) 所有位都是一:0xFFFFFFFF
1字节 Coinbase 数据大小(Coinbase Data Size) Coinbase 数据的长度,从 2 到 100 字节
可变长度 Coinbase 数据(Coinbase Data) 任意数据,用于额外的 nonce 和挖矿标签;在 v2 块中,必须以区块高度开头
4字节 序列号(Sequence Number) 设置为0xFFFFFFFF

在 coinbase 交易中,前两个字段的值不代表 UTXO 引用。第一个字段不是“交易哈希”,而是填充了 32 字节,全部设置为零。而“输出索引”填充了 4 字节,全部设置为 0xFF(十进制 255)。输入脚本被 coinbase 数据替代,这是矿工使用的数据字段,我们将在接下来看到。

Coinbase数据

Coinbase 交易没有输入脚本字段。相反,这个字段被 coinbase 数据替换,其长度必须在 2 到 100 字节之间。除了前几个字节外,coinbase 数据的其余部分可以由矿工任意使用;它是任意的数据。

例如,在创世区块中,中本聪在 coinbase 数据中添加了文本“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”,将其作为这个区块可能创建的最早日期的证明,并传递了一条信息。目前,矿工经常使用 coinbase 数据来包含额外的 nonce 值和标识矿池的字符串。

coinbase 的前几个字节曾经是任意的,但现在不再是这样了。根据 BIP34,版本 2 的区块(版本字段设置为 2 或更高)必须在 coinbase 字段的开头作为脚本“push”操作包含区块高度。

构建区块头

构建区块头,挖矿节点需要填写六个字段,如表12-3所列。

表 12-3. 区块头结构

大小 字段 描述
4字节 版本(Version) 一个多功能的位字段
32字节 前一区块哈希(Previous Block Hash) 指向链中前一(父)区块的哈希的引用
32字节 默克尔根(Merkle Root) 这个区块交易的默克尔树的根节点哈希
4字节 时间戳(Timesteamp) 此区块的大致创建时间(自Unix纪元起的秒数)
4字节 目标(Target) 此区块的工作量证明算法目标
4字节 唯一随机数(Nonce) 一个用于工作量证明算法的计数器

版本字段最初是一个整数字段,并在比特币网络的三次升级中使用,这些升级定义在BIPs 34、66和65中。每次升级时,版本号都会递增。后来的升级将版本字段定义为位字段,称为versionbits,允许同时进行最多29个升级;详情请参阅“BIP9:信号和激活”第298页。更晚一些,矿工开始使用一些versionbits作为辅助随机数字段。

注意:BIPs 34、66和65中定义的协议升级是按照这个顺序依次发生的,BIP66(严格的DER)在BIP65(OP_CHECKTIMELOCKVERIFY)之前发生,因此比特币开发人员通常按照这个顺序而不是按照数字顺序列出它们。

今天,versionbits字段没有意义,除非正在进行升级共识协议的尝试,在这种情况下,你将需要阅读其文档,以确定它如何使用versionbits。

接下来,挖矿节点需要添加“上一个区块哈希”(也称为prevhash)。这是来自网络的上一个块的块头哈希,Jing的节点已经接受并选择为他的候选块的父块。

注意:通过选择候选块头中的上一个区块哈希字段所指示的特定父区块,Jing将他的挖矿算力承诺到扩展以该特定区块结尾的链上。

下一步是使用默克尔树来承诺所有交易。每个交易都按拓扑顺序列出,使用其见证交易标识符(wtxid),其中32个0x00字节代表第一个交易(coinbase)的wtxid。正如我们在“Merkle Trees”中看到的,如果有奇数个wtxid,则最后一个wtxid将与自身哈希,创建每个包含一个交易哈希的节点。然后,交易哈希按成对组合,创建树的每个级别,直到所有交易被总结到树的“根”节点中。Merkle树的根将所有交易总结为一个单一的32字节值,即见证根哈希。

见证根哈希被添加到coinbase交易的输出中。如果块中的交易不需要包含见证结构,则可以跳过此步骤。然后,每个交易(包括coinbase交易)都使用其交易标识符(txid)进行列出,并用于构建第二个Merkle树,其根成为Merkle根,对其进行承诺到块头中。

Jing的挖矿节点然后添加一个4字节的时间戳,编码为Unix“纪元”时间戳,它是基于从1970年1月1日UTC/GMT午夜开始经过的秒数。

然后,Jing的节点填写nBits目标,它必须设置为使其成为有效块所需的PoW的紧凑表示。目标以“目标位”度量存储在块中,它是目标的尾数-指数编码。编码有一个字节的指数,后跟一个3字节的尾数(系数)。例如,在块277,316中,目标位值为0x1903a30c。第一部分0x19是十六进制指数,而下一部分0x03a30c是系数。目标的概念在“Retargeting to Adjust Difficulty”中解释,“目标位”表示在“Target Representation”中解释。

最后一个字段是随机数,初始化为零。

填写了所有其他字段后,候选块的头部现在已经完成,挖矿过程可以开始。目标现在是找到一个导致哈希小于目标的头部。挖矿节点将需要测试数十亿或数万亿个头部的变化,直到找到满足要求的版本。

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

0 条评论

请先 登录 后评论