比特币源码研读(2)数据结构-区块Block

上一篇基本认识了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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容