比特币源码分析--深入理解区块链 12. 区块链墨克树的使用

        区块(Block)是区块链的最基本的数据单元,区块链是由一个个在逻辑上前后链接的区块构成。区块常用高度(height)来表示,从0开始(高度为0的区块是创世块),截止目前比特币区块高度是736311,并且按照每10分钟左右生成一个新块的频率在递增。为了达到高效安全的验证区块中的数据,保护区块免遭任何形式的破坏和篡改,比特币采用了哈希树(hash tree;Merkle tree:墨克树)的作为区块的数学数据结构。墨克树能够高效、安全地验证大型数据结构的内容,是哈希链的推广形式。 该概念由瑞夫·墨克于 1979 年申请专利,故亦称墨克树(Merkle tree)。在详述之前,我们先了解比特币区块的基本数据结构:

比特币区块的基本数据结构

区块主要由区块头(Block Header)和交易信息(Transaction,简称Tx)组成。区块哈希(Block Hash)由根据区块头计算出的Hash256,它是唯一的。每一个区块中有至少一条或以上的交易信息,如下图:

区块中的交易

        这里从Tx0~Txn表示有n条交易信息,每个区块的首条交易,也就是Tx0固定是Coinbse交易。Coinbase交易是矿工创建的,主要是为了奖励矿工进行 POW 挖矿而付出的努力。根据前文POW工作量原理说明,也就是该奖励给那个率先计算出符合工作量要求的矿工。奖励分为两部分。一部分是出块奖励,这部分是相对固定的,每四年减半一次。另外一部分是手续费,当前区块的每个交易中都会包含一定的对矿工的奖励,也就是交易手续费。创建 Coinbase 交易的时候,矿工会把所有交易中的手续费累加到一起,然后把这笔钱转账给自己。

区块的链式结构

区块的链式结构

        区块头(Block Header)中的字段hashPrevBlock标记了它的上一个区块的Hash值,这样整个区块在逻辑上就组成了一个单向链表。区块哈希(Block Hash)是在区块同步过程中根据Block Header动态计算出来的,而不是一个静态的数据,因此可以防止区块在不同的节点之间同步的过程中被篡改,一旦某个节点接收的区块数据校验无法通过,将直接导致同步失败。

 墨克树的组成

        墨克树是二叉哈希树,它的表现形式是一个二叉树。其中叶子节点就是区块中的交易信息,一个理想的墨克树如下图所示:

典型的墨克树

        上图中的墨克树共有4个叶子节点,也就是说4条交易信息。每条交易信息使用Hash256根据交易的详细数据计算出对应的哈希值,对应的分别是Hash0~Hash3。然后再两两组合得到Hash01和Hash23,其中Hash01=Hash0 + Hash1,它的含义是根据Hash0和Hash1的数据计算出Hash01。最后根部的Merkle Root就是最后两个子节点哈希值合并后结算出的哈希值。

          上述的交易数量是偶数,并且中间的哈希个数也是偶数。两个组合很容易理解。但是如果是奇数的话,如下图所示:

 

 墨克树特别形式

        上图有6条交易信息,当两个组合后计算出来的中间哈希值只有3个,如果再进一步计算哈希值时,两个组合后只剩下一个。根据比特币对墨克根的计算方法,它会增加一个重复的Hash45,组合后计算最终的Merkle Root。如下图所示:

墨克树特别形式 

        上图作为合法的交易,比特币的节点在接收到该区块数据时通过计算根墨克哈希值,很容易就验证其下面的Tx数据是否有篡改。但采用这种算法有一个致命的缺陷,如果攻击者伪造两个相同的交易,增加Tx6和Tx7分别与Tx4和Tx5完全一致,就可以与上述的结果完全一致,并且校验也能通过。如下图所示:

 墨克树的重复

        现在攻击者通过复制Tx4、Tx5可与上述一样的结果,理论上可欺骗其它的比特币点,将该区块数据视为合法的。但是实际上比特币节点在同步区块数据时将检查Tx的重复性,如果发现重复的数据即视为非法。

交易有效性检查

        为了检查交易的有效性,在同步区块过程中将对交易的有效性进行检查,在github的代码:bitcoin/tx_check.cpp at master · bitcoin/bitcoin · GitHub

中通过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, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
        return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-oversize");

    // Check for negative or overflow output values (see CVE-2010-5139)
    CAmount nValueOut = 0;
    for (const auto& txout : tx.vout)
    {
        if (txout.nValue < 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<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() < 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;
}

上述代码中,就针对了重复交易进行检查,通过对交易输入(Tx in)的prevout是否重复来判断交易是否存在重复。

墨克根(Merkle Root)的计算过程

https://github.com/bitcoin/bitcoin/blob/master/src/consensus/merkle.cpp

代码中,我们可以看到墨克根的计算代码:

uint256 BlockMerkleRoot(const CBlock& block, bool* mutated)
{
    std::vector<uint256> leaves;
    leaves.resize(block.vtx.size());
    for (size_t s = 0; s < block.vtx.size(); s++) {
        leaves[s] = block.vtx[s]->GetHash();
    }
    return ComputeMerkleRoot(std::move(leaves), mutated);
}

uint256 ComputeMerkleRoot(std::vector<uint256> hashes, bool* mutated) {
    bool mutation = false;
    while (hashes.size() > 1) {
        if (mutated) {
            for (size_t pos = 0; pos + 1 < hashes.size(); pos += 2) {
                if (hashes[pos] == hashes[pos + 1]) mutation = true;
            }
        }
        if (hashes.size() & 1) {
            hashes.push_back(hashes.back());
        }
        SHA256D64(hashes[0].begin(), hashes[0].begin(), hashes.size() / 2);
        hashes.resize(hashes.size() / 2);
    }
    if (mutated) *mutated = mutation;
    if (hashes.size() == 0) return uint256();
    return hashes[0];
}

        代码中的函数BlockMerkleRoot首先计算叶子节点,也就是每条交易数据的Hash值。然后根据这些Hash计算Merkle Root。其中hashes.size() & 1表示哈希的个数是奇数时,在容器中添加一个与末尾一样的hash。通过SHA256D64计算HASH256。将计算的结果保存到hashes[0],然后重复该过程最终计算出墨克根(Merkle Root)。

交易输入Tx Input和交易输出Tx Output的数据结构

        每笔交易(Tx) 里面包含交易输入(Tx Input)和交易输出(Tx Output)的信息。交易输入实际上包含了前面的交易的输出的信息,前面的交易是指当前交易所在的区块往前的区块,不一定是相邻的区块。交易信息通过这样的链接,就可以进行追溯资金的来源。下图是交易的链式数据结构:

 比特币的链式交易数据结构

        在Tx input中,有一个类型为COutPoint的prevout,它包含了一个Hash值,指向前面的区块的交易输出对应的Tx id(Tx id就是Tx hash值)。从而使该交易可以往后溯源。也就是说当前交易输入的资金来源不是凭空产生的,必须能够找到与其对应的交易输出。但是对于Coinbase矿工挖矿的奖励的部分,它的Tx Input中的prevout是空,表明该交易是挖矿奖励,是系统生成的。而不是来自其他账户的交易转账。

创世区块

        创世区块(Genesis Block)是比特币区块链的第一个区块,它写死在代理里面,该区块共有50个比特币的奖励,不能用于交易,也不属于任何人。通过分析代码我们可以看出,该区块的时间是Saturday, January 3, 2009 6:15:05 PM,距今13年。区块的数据如下:
Nonce = 2083236893,

nBits = 0x1d00ffff,

nVersion = 1

Block Hash :

0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

hashMerkleRoot :

0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b

猜你喜欢

转载自blog.csdn.net/dragon_trooquant/article/details/124787293
今日推荐