Skip to content

Latest commit

 

History

History
348 lines (297 loc) · 11.1 KB

精通比特币之六区块链.md

File metadata and controls

348 lines (297 loc) · 11.1 KB

精通比特币之六区块链


一、区块


区块链之所以称做区块链,其实就是一个类似链表的东西,新区块通过记载父区块的哈希,以此来不断的迭代形成一条链状的数据结构。先看一下区块的数据结构:

class CBlockHeader
{
public:
    // header
    static const int32_t CURRENT_VERSION=4;
    int32_t nVersion;
    uint256 hashPrevBlock;
    uint256 hashMerkleRoot;
    uint32_t nTime;//时间戳
    uint32_t nBits;//难度
    uint32_t nNonce;//计算HASH的临时数

    CBlockHeader()
    {
        SetNull();
    }

    ADD_SERIALIZE_METHODS;

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

    void SetNull()
    {
        nVersion = CBlockHeader::CURRENT_VERSION;
        hashPrevBlock.SetNull();
        hashMerkleRoot.SetNull();
        nTime = 0;
        nBits = 0;
        nNonce = 0;
    }

    bool IsNull() const
    {
        return (nBits == 0);
    }

    uint256 GetHash() const;
    //注释掉的原因有特殊字符 fjf 显示不正确
    //int64_t GetBlockTime() const{return (int64_t)nTime;}
};
class CBlock : public CBlockHeader
{
public:
    // network and disk
    std::vector<CTransaction> vtx;

    // memory only
    mutable std::vector<uint256> vMerkleTree;

    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, int nType, int nVersion) {
        READWRITE(*(CBlockHeader*)this);
        READWRITE(vtx);
    }

    void SetNull()
    {
        CBlockHeader::SetNull();
        vtx.clear();
        vMerkleTree.clear();
    }

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

    // Build the in-memory merkle tree for this block and return the merkle root.
    // If non-NULL, \*mutated is set to whether mutation was detected in the merkle
    // tree (a duplication of transactions in the block leading to an identical merkle root).
    uint256 BuildMerkleTree(bool\* mutated = NULL) const;

    std::vector<uint256> GetMerkleBranch(int nIndex) const;
    static uint256 CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
    std::string ToString() const;
};

区块的数据这里不做详细的说明,毕竟书上和各种资料上都有。而且确实也没有啥可讲的。需要说明的是,得到区块有两种方式,一个是区块本身的啥希值,一个是高度。前者其实也就是区块头哈希值,区块本身不保存这个值。 区块高度也有需要注意的地方,就是同一个高度,可能有多个区块(软分叉和硬分叉)。
那么查找区块和高度怎么办呢?需要另外一个数据结构:
/** The block chain is a tree shaped structure starting with the
 * genesis block at the root, with each block potentially having multiple
 * candidates to be the next block. A blockindex may have multiple pprev pointing
 * to it, but at most one of them can be part of the currently active branch.
 */
class CBlockIndex
{
public:
    //! pointer to the hash of the block, if any. Memory is owned by this CBlockIndex
    const uint256* phashBlock;

    //! pointer to the index of the predecessor of this block
    CBlockIndex* pprev;

    //! pointer to the index of some further predecessor of this block
    CBlockIndex* pskip;

    //! height of the entry in the chain. The genesis block has height 0
    int nHeight;

    //! Which # file this block is stored in (blk?????.dat)
    int nFile;

    //! Byte offset within blk?????.dat where this block's data is stored
    unsigned int nDataPos;

    //! Byte offset within rev?????.dat where this block's undo data is stored
    unsigned int nUndoPos;

    //! (memory only) Total amount of work (expected number of hashes) in the chain up to and including this block
    arith_uint256 nChainWork;

    //! Number of transactions in this block.
    //! Note: in a potential headers-first mode, this number cannot be relied upon
    unsigned int nTx;

    //! (memory only) Number of transactions in the chain up to and including this block.
    //! This value will be non-zero only if and only if transactions for this block and all its parents are available.
    //! Change to 64-bit type when necessary; won't happen before 2030
    unsigned int nChainTx;

    //! Verification status of this block. See enum BlockStatus
    uint32_t nStatus;

    //! block header
    int32_t nVersion;
    uint256 hashMerkleRoot;
    uint32_t nTime;
    uint32_t nBits;
    uint32_t nNonce;

    //! (memory only) Sequential id assigned to distinguish order in which blocks are received.
    int32_t nSequenceId;

    //! (memory only) Maximum nTime in the chain up to and including this block.
    unsigned int nTimeMax;

    void SetNull()
    {
        phashBlock = nullptr;
        pprev = nullptr;
        pskip = nullptr;
        nHeight = 0;
        nFile = 0;
        nDataPos = 0;
        nUndoPos = 0;
        nChainWork = arith_uint256();
        nTx = 0;
        nChainTx = 0;
        nStatus = 0;
        nSequenceId = 0;
        nTimeMax = 0;

        nVersion       = 0;
        hashMerkleRoot = uint256();
        nTime          = 0;
        nBits          = 0;
        nNonce         = 0;
    }

    CBlockIndex()
    {
        SetNull();
    }

    explicit CBlockIndex(const CBlockHeader& block)
    {
        SetNull();

        nVersion       = block.nVersion;
        hashMerkleRoot = block.hashMerkleRoot;
        nTime          = block.nTime;
        nBits          = block.nBits;
        nNonce         = block.nNonce;
    }

    CDiskBlockPos GetBlockPos() const {
        CDiskBlockPos ret;
        if (nStatus & BLOCK_HAVE_DATA) {
            ret.nFile = nFile;
            ret.nPos  = nDataPos;
        }
        return ret;
    }

    CDiskBlockPos GetUndoPos() const {
        CDiskBlockPos ret;
        if (nStatus & BLOCK_HAVE_UNDO) {
            ret.nFile = nFile;
            ret.nPos  = nUndoPos;
        }
        return ret;
    }

    CBlockHeader GetBlockHeader() const
    {
        CBlockHeader block;
        block.nVersion       = nVersion;
        if (pprev)
            block.hashPrevBlock = pprev->GetBlockHash();
        block.hashMerkleRoot = hashMerkleRoot;
        block.nTime          = nTime;
        block.nBits          = nBits;
        block.nNonce         = nNonce;
        return block;
    }

.......
    //! Efficiently find an ancestor of this block.
    CBlockIndex* GetAncestor(int height);
    const CBlockIndex* GetAncestor(int height) const;
};

需要注意的是CBlockIndex实例仅保存在内存当中。若要将区块索引存入磁盘,则需要其子类CDiskBlockIndex。从网络上得到的区块信息是直接落盘的。所以这时候会对其进行处理,从而更新CBlockIndex

二、Merkle 树


Merkle树部分主要代码在consensus目前下的merkle.h merkle.cpp两个文件内,分为几种情况来计算,本树的计算方法比较简单,就是两两哈希,直到结果为1,如果起始不是偶数,搞成偶数就可以了。方法是复制最后一个到末尾。
SPV轻量钱包可以调用BlockMerkleBranch来得到结果验证相关的数据。验证的方法是发送消息从全节点得到相关的完整树的相关部分,再用ComputeMerkleRootFromBranch计算相关哈希值与得到的进行匹配验证即可。
代码在 net_processing.cpp 中的INV消息数据中。
ProcessMessage消息处理函数中
bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc)
{
else if (strCommand == NetMsgType::GETDATA)
{
......
    ProcessGetData(pfrom, chainparams.GetConsensus(), connman, interruptMsgProc);
}
}
ProcessGetData中调用:
void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParams, CConnman* connman, const std::atomic<bool>& interruptMsgProc)
{
if (it != pfrom->vRecvGetData.end() && !pfrom->fPauseSend) {
    const CInv &inv = \*it;
    if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) {
        it++;
        ProcessGetBlockData(pfrom, consensusParams, inv, connman, interruptMsgProc);
    }
}
}
void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensusParams, const CInv& inv, CConnman* connman, const std::atomic<bool>& interruptMsgProc)
{
  .......
  if (inv.type == MSG_BLOCK)
      connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock));
  else if (inv.type == MSG_WITNESS_BLOCK)
      connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock));
  else if (inv.type == MSG_FILTERED_BLOCK)
  {
      bool sendMerkleBlock = false;
      CMerkleBlock merkleBlock;
      {
          LOCK(pfrom->cs_filter);
          if (pfrom->pfilter) {
              sendMerkleBlock = true;
              merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter);
          }
      }
      if (sendMerkleBlock) {
          connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock));
          // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see
          // This avoids hurting performance by pointlessly requiring a round-trip
          // Note that there is currently no way for a node to request any single transactions we didn't send here -
          // they must either disconnect and retry or request the full block.
          // Thus, the protocol spec specified allows for us to provide duplicate txn here,
          // however we MUST always provide at least what the remote peer needs
          typedef std::pair<unsigned int, uint256> PairType;
          for (PairType& pair : merkleBlock.vMatchedTxn)
              connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first]));
      }
      // else
          // no response
  }
  else if (inv.type == MSG_CMPCT_BLOCK)
  ......
}

SPV钱包拉到数据后,就可以按上面讲的进行验证了。

三、比特币网络的类型


除了主网,主要有三种辅助网络:使用testnet,测试网络;Segnet—隔离见证测试网络;Regtest--本地区块链
启动方法类似: $ bitcoind -testnet
使用方法类似:$ bitcoin-cli -testnet getinfo