区块链比特币学习 - 7 - 区块

Posted 宣之于口

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区块链比特币学习 - 7 - 区块相关的知识,希望对你有一定的参考价值。

比特币学习 - 7 - 区块

参考文章:精通比特币 and Merkle Tree学习 and Merkle 验证

一、简介

区块链是由包含交易信息的区块从后向前有序链接起来的数据结构。它可以被存储为flat file(一种包含没有相对关系记录的文件),或是存储在一个简单数据库中。比特币核心客户端使用Google的LevelDB数据库存储区块链元数据。区块被从后向前有序地链接在这个链条里,每个区块都指向前一个区块。

区块高度:是指该区块在区块链中的位置。区块高度并不是唯一的标识符。虽然一个单一的区块总是会有一个明确的、固定的区块高度,但反过来却并不成立,一个区块高度并不总是识别一个单一的区块。两个或两个以上的区块可能有相同的区块高度,在区块链里争夺同一位置。其中创世区块的高度为0。

二、区块结构

区块是一种被包含在公开账簿(区块链)里的聚合了交易信息的容器数据结构。它由一个包含元数据的区块头和紧跟其后的构成区块主体的一长串交易组成。区块头是80字节,而平均每个交易至少是250字节,而且平均每个区块至少包含超过500个交易。

1. 区块头

区块头由三组区块元数据组成。首先是一组引用父区块哈希值的数据,这组元数据用于将该区块与区块链中前一区块相连接。第二组元数据,即难度、时间戳和nonce,与挖矿竞争相关。第三组元数据是merkle树根(一种用来有效地总结区块中所有交易的数据结构)。

a.数据结构
class CBlockHeader

public:
    int32_t nVersion;			// 版本号, 用于跟踪软件/协议的更新
    uint256 hashPrevBlock;		// 父区块哈希值, 引用区块链中父区块的哈希值
    uint256 hashMerkleRoot;		// Merkle根, 该区块中交易的merkle树根的哈希值
    uint32_t nTime;				// 时间戳, 该区块产生的近似时间(精确到秒的Unix时间戳)
    uint32_t nBits;				// 难度目标, 该区块工作量证明算法的难度目标
    uint32_t nNonce;			// Nonce, 用于工作量证明算法的计数器
    ...

Nonce、难度目标和时间戳会用于挖矿过程,这里不进行展开。

b. 区块哈希值

区块哈希值可以唯一、明确地标识一个区块,并且任何节点通过简单地对区块头进行哈希计算都可以独立地获取该区块哈希值

计算:通过SHA256算法对区块头进行二次哈希计算而得到的数字指纹,产生的32字节哈希值被称为区块哈希值

存储:区块哈希值实际上并不包含在区块的数据结构里,不管是该区块在网络上传输时,抑或是它作为区块链的一部分被存储在某节点的永久性存储设备上时。相反,区块哈希值是当该区块从网络被接收时由每个节点计算出来的。区块的哈希值可能会作为区块元数据的一部分被存储在一个独立的数据库表中,以便于索引和更快地从磁盘检索区块。

2. 区块体

区块体是交易的集合,Merkle树是一种哈希二叉树,它是一种用作快速归纳和校验大规模数据完整性的数据结构。

a. 数据结构
class CBlock : public CBlockHeader

public:
    std::vector<CTransactionRef> vtx;	// 交易集合
    mutable bool fChecked;    			// memory only
 
b. Merkle

梅克尔树是一种二叉树,由于它能快速检查和归纳大量数据,被用在区块中记录交易记录的完整性。

下一节进行详细描述。

三、梅克尔树

1. 基本概念

区块头中的merkle根即根据交易计算出, 即HASH0:

注意hash() 函数实际是进行两次的SHA256运算 Hash(L1) == SHA256(SHA256(L1))

代码目录:/src/consensus/merkle.cpp

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];

2. 数据结构

class CMerkleBlock

public:
    CBlockHeader header;			// 区块头
    CPartialMerkleTree txn;			// 梅克尔验证路径
	...
;

验证路径:

class CPartialMerkleTree

protected:
    unsigned int nTransactions;			// 块中交易总数
    std::vector<bool> vBits;			// node-is-parent-of-matched-txid bits 
    std::vector<uint256> vHash;			// 交易哈希列表
    bool fBad;							// flag set when encountering invalid data

    unsigned int CalcTreeWidth(int height) const 			//辅助函数,有效地计算在给定高度的merkle树中的节点数
        return (nTransactions+(1 << height)-1) >> height;
    

    uint256 CalcHash(int height, unsigned int pos, const std::vector<uint256> &vTxid);	//计算merkle树中节点的哈希

    void TraverseAndBuild(int height, unsigned int pos, const std::vector<uint256> &vTxid, const std::vector<bool> &vMatch);			// 递归函数,它遍历树节点,以交易编号和哈希的形式存储数据
	...

2. 验证步骤

SPV节点没有交易数据,而是只保存每一个区块的区块头。所以如果需要进行交易验证的时候,而本地又没有这个交易的信息,SPV就会发送一条广播,让其他完整节点给它发送验证数据,bitcoin中,这类数据类型为MerkleBlock,下面描述一下验证过程。

a. 发起请求

第一步:SPV拿到一个交易信息之后,并不能确认这个交易是否合法,因此要对这个交易的输入进行验证。但是它只拿到了单个交易的信息,而没有本地的完整区块链数据,因此,SPV要拿着这个交易的信息向网络发起查询请求,这个请求的response称为merkle block message

b. 验证路径

第二步:当其他有完整区块链数据的客户端收到这个请求之后,利用传过来的交易信息在自己的区块链数据库中进行查询,并把目标区块的区块头和一条验证路径的交易串(交易编号和哈希)返回给请求源。这样节点就可以通过下图所示的方法进行验证了。(红色区块就是发送过来的交易)

以上是关于区块链比特币学习 - 7 - 区块的主要内容,如果未能解决你的问题,请参考以下文章

区块链初学者:哈希算法与默克尔树1小时入门 | 视频

区块链区块链技术学习总结

区块链精通比特币学习笔记

区块链技术学习指引

区块链比特币学习 - 1 - 交易

区块链比特币学习 - 5 -创币交易