比特幣原始碼研讀(2)資料結構-區塊Block

chaors發表於2018-04-25

上一篇基本認識了bitcoin原始碼結構和個模組程式碼的功能,今天看區塊。

區塊是組成區塊的基本單位,我們可以通過bitcoin-cli命令檢視一個區塊的基本資訊。

bitcli-cli獲取區塊資訊

接下來我們就在原始碼找一下區塊的定義,由於我們並不知道區塊定義在哪。我們試著全域性搜一下block.h(或block.cpp):

搜尋block.h

進去發現還真被我找到了,其實我們在上一篇的bitcoin原始碼結構的目錄結構裡已經說過./private目錄下是區塊類和交易類的實現。接下來,就讓我們一窺block的究竟。

原始碼初窺

CBlockHeader

/** Nodes collect new transactions into a block, hash them into a hash tree,
 * and scan through nonce values to make the block's hash satisfy proof-of-work
 * requirements.  When they solve the proof-of-work, they broadcast the block
 * to everyone and the block is added to the block chain.  The first transaction
 * in the block is a special one that creates a new coin owned by the creator
 * of the block.
 *
 **網路中的節點不斷收集新的交易打包到區塊中,所有的交易會通過兩兩雜湊的方式形成一個Merkle樹
 * 打包的過程就是要完成工作量證明的要求,當節點解出了當前的隨機數時,
 * 它就把當前的區塊廣播到其他所有節點,並且加到區塊鏈上。
 * 區塊中的第一筆交易稱之為CoinBase交易,是產生的新幣,獎勵給區塊的產生者  
 * 
 * add by chaors 20180419
 */

class CBlockHeader
{
public:
    // header
    int32_t nVersion;       //版本
    uint256 hashPrevBlock;  //上一個區塊的hash
    uint256 hashMerkleRoot; //包含交易資訊的Merkle樹根
    uint32_t nTime;         //時間戳
    uint32_t nBits;         //工作量證明(POW)的難度
    uint32_t nNonce;        //要找的符合POW的隨機數

    CBlockHeader()          //建構函式初始化成員變數
    {
        SetNull();          
    }

    ADD_SERIALIZE_METHODS;  //通過封裝的模板實現類的序列化

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(this->nVersion);
        READWRITE(hashPrevBlock);
        READWRITE(hashMerkleRoot);
        READWRITE(nTime);
        READWRITE(nBits);
        READWRITE(nNonce);
    }

    void SetNull()          //初始化成員變數
    {
        nVersion = 0;
        hashPrevBlock.SetNull();
        hashMerkleRoot.SetNull();
        nTime = 0;
        nBits = 0;
        nNonce = 0;
    }

    bool IsNull() const
    {
        return (nBits == 0);     //難度為0說明區塊還未建立,區塊頭為空
    }

    uint256 GetHash() const;     //獲取雜湊

    int64_t GetBlockTime() const //獲取區塊時間
    {
        return (int64_t)nTime;
    }
};
複製程式碼

CBlock

class CBlock : public CBlockHeader         //繼承自CBlockHeader,擁有其所有成員變數
{
public:
    // network and disk
    std::vector<CTransactionRef> vtx;      //所有交易的容器

    // memory only
    mutable bool fChecked;                 //交易是否驗證

    CBlock()
    {
        SetNull();
    }

    CBlock(const CBlockHeader &header)
    {
        SetNull();
        *((CBlockHeader*)this) = header;
    }

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(*(CBlockHeader*)this);
        READWRITE(vtx);
    }

    void SetNull()
    {
        CBlockHeader::SetNull();
        vtx.clear();
        fChecked = false;
    }

    CBlockHeader GetBlockHeader() const
    {
        CBlockHeader block;
        block.nVersion       = nVersion;
        block.hashPrevBlock  = hashPrevBlock;
        block.hashMerkleRoot = hashMerkleRoot;
        block.nTime          = nTime;
        block.nBits          = nBits;
        block.nNonce         = nNonce;
        return block;
    }

    std::string ToString() const;
};
複製程式碼

CBlockLocator


/** Describes a place in the block chain to another node such that if the
 * other node doesn't have the same branch, it can find a recent common trunk.
 * The further back it is, the further before the fork it may be.
 *
 **描述區塊鏈中在其他節點的一個位置,
 *如果其他節點沒有相同的分支,它可以找到一個最近的中繼(最近的相同塊)。
 *更進一步地講,它可能是分叉前的一個位置
 */
struct CBlockLocator
{
    std::vector<uint256> vHave;

    CBlockLocator() {}

    explicit CBlockLocator(const std::vector<uint256>& vHaveIn) : vHave(vHaveIn) {}

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        int nVersion = s.GetVersion();
        if (!(s.GetType() & SER_GETHASH))
            READWRITE(nVersion);
        READWRITE(vHave);
    }

    void SetNull()
    {
        vHave.clear();
    }

    bool IsNull() const
    {
        return vHave.empty();
    }
};
複製程式碼

.cpp函式

uint256 CBlockHeader::GetHash() const
{
    return SerializeHash(*this);        //生成256位的雜湊值
}

std::string CBlock::ToString() const    //區塊物件格式化字串輸出
{
    std::stringstream s;
    s << strprintf("CBlock(hash=%s, ver=0x%08x, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%u)\n",
        GetHash().ToString(),
        nVersion,
        hashPrevBlock.ToString(),
        hashMerkleRoot.ToString(),
        nTime, nBits, nNonce,
        vtx.size());
    for (const auto& tx : vtx) {
        s << "  " << tx->ToString() << "\n";
    }
    return s.str();
}
複製程式碼

區塊結構分析

區塊是通過前後連結構成區塊鏈的一種容器資料結構。它由描述區塊主要資訊的區塊頭和包含若干交易資料的區塊共同組成。區塊頭是80位元組,而平均每個交易至少是250位元組,而且平均每個區塊至少包含超過500個交易。所以,一個區塊是比區塊頭大好多的資料體。這也是比特幣驗證交易是否存在採用Merkcle樹的原因。

整體結構

資料項 大小(Byte) 描述
Block Size 4 區塊大小
Block Header 80 區塊頭資訊大小
Transactions m*n(n>=250) 所有交易的列表
Transactions Counter 1-9 交易數額

比特幣的區塊大小目前被嚴格限制在1MB以內。4位元組的區塊大小欄位不包含在此內!

區塊頭

資料項 大小(Byte) 儲存方式 描述
Version 4 小端 區塊版本,規定了區塊遵守的驗證規則
Previous Block Hash 32 內部位元組順序 上一個區塊雜湊值(SHA256 (SHA256(Block Header)))
Merkle Root 32 內部位元組順序 Merkle樹根,包含了所有交易的雜湊
Timestamp 4 小端 區塊產生時間戳,大於前11個區塊時間戳的平均值,全節點會拒絕時間戳超出自己2小時的區塊
nBitS 4 小端 工作量證明(POW)的目標難度值,當前區塊難度值需要經過Target nBits編碼才能轉化為目標雜湊值
Nonce 4 小端 用於POW的一個隨機數,隨著算力增大可能會導致Nonce位數不夠 協議規定時間戳和CoinbaseTransaction資訊可修改用於擴充套件Nonce位數

區塊識別符號

  • BlockHash 區塊雜湊值,是通過SHA256演算法對區塊頭資訊進行雜湊得到的,這個值必須滿足POW的DifficultyTarget,該區塊才被認為有效。同時,也是區塊的唯一識別符號,可以通過通過bitcoin-cli根據BlockHash查詢區塊資訊(文章開頭我們就使用過)

  • BlockHeight 區塊高度,是用來標示區塊在區塊鏈中的位置。創世區塊高度為0,每一個加在後面的區塊,區塊高度遞增1。可以通過bitcoin-cli根據高度查詢區塊雜湊值(文章開頭我們就使用過)

注:BlockHeight並不是唯一識別符號,當區塊鏈出現臨時分叉時,會有兩個區塊的高度值相同的情況。

創世區塊

區塊鏈上第一個區塊被稱為創世區塊,它是所有區塊的共同祖先。我們可以檢視下比特幣的創世區塊:

創世區塊

比特幣創始人聰哥在創世區塊包含了一個隱藏的資訊。在其Coinbase交易的輸入中包含這樣一句話“The Times 03/Jan/2009 Chancellor on brink of second bailout forbanks.”這句話是泰晤士報當天的頭版文章標題,聰哥這樣做的目的不得而知。但是,這樣一條非交易資訊可以輕而易舉地插入比特幣,這個現象值得深思。如此,就不難理解前不久曝光的"不法分子利用比特幣儲存兒童色情內容"新聞,當然這種儲存可能遠比聰哥的那句話要更復雜一點。

思考

    1. 我們檢視的區塊資訊中,以下不在原始碼結構中的欄位有什麼含義?為什麼他們不在原始碼定義的區塊結構中? confirmations strippedsize weight mediantime bits chainwork
  • 2.為什麼區塊的雜湊沒有定義在區塊頭內部?

注:以上問題可能我也沒答案,可以留言我們一起交流共同進步。

對區塊的認識就告一段落,下一篇準備去探索比特幣資料結構-交易的結構。

參考文獻

. . . .

###網際網路顛覆世界,區塊鏈顛覆網際網路!

--------------------------------------------------20180419 23:20

相關文章