比特币源码情景分析之区块数据结构

xiaoxiao2021-02-28  42

比特币源码情景分析之区块数据结构

磁盘中区块(block)数据结构

区块由区块头+交易数据构成, 所以CBlock是继承CBlockHeader的 class CBlock : public CBlockHeader { public:     // network and disk     std::vector<CTransactionRef> vtx;     // memory only     mutable bool fChecked; } class CBlockHeader { public:     // header     int32_t nVersion;     uint256 hashPrevBlock;     uint256 hashMerkleRoot;     uint32_t nTime;     uint32_t nBits;     uint32_t nNonce; } 交易数据 class CTransaction { public:     // Default transaction version.     static const int32_t CURRENT_VERSION=2;     // Changing the default transaction version requires a two step process: first     // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date     // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and     // MAX_STANDARD_VERSION will be equal.     static const int32_t MAX_STANDARD_VERSION=2;     // The local variables are made const to prevent unintended modification     // without updating the cached hash value. However, CTransaction is not     // actually immutable; deserialization and assignment are implemented,     // and bypass the constness. This is safe, as they update the entire     // structure, including the hash.     const std::vector<CTxIn> vin;     const std::vector<CTxOut> vout;     const int32_t nVersion;     const uint32_t nLockTime; private:     /** Memory only. */     const uint256 hash; }

一个交易有多个输入txin和多个输出txout构成

class CTxIn { public:      COutPoint prevout;     CScript scriptSig;     uint32_t nSequence;     CScriptWitness scriptWitness; //! Only serialized through CTransaction } 交易输出txout,也就是UTXO(Unspent Transaction Output) class CTxOut { public:     CAmount nValue;     CScript scriptPubKey; } 上面的数据结构以文件的形式顺序存在~/.bitcoin/block/xxx.data

运行时区块(block)数据结构

    上面的区块占用空间太大,运行时从磁盘读取出来保存在内存中不现实,因此系统运行时只加载blockheader, block的交易内容数据是动态按需加载.运行时的block对象是CBlockIndex,它的nFile(哪个文件)和mDataPos(文件的位置)字段指明区块内容的储存信息 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; }     CBlockIndex只包含区块头信息,同时新增pprev信息来维护链表结构.同时通过nFile,nDataPos间接引用区块交易数据,当需要交易信息的时候通过DataPos, nFile信息就可以从对应文件读取出来。CBlockIndex对应的存储结构是CDiskBlockIndex,多一个hashPrev字段, 这个字段对应CBlockIndex里的pprev,由于对象指针pprev存在数据库没有意义,只能存储hash(hashPrev class CDiskBlockIndex : public CBlockIndex { public:     uint256 hashPrev; } CBlockIndex对象会通过pblocktree对象写入到数据库中,pblocktree将CBlockIndex转化为CDiskBlockIndex存储在leveldb等数据库里,数据库文件为~/.bitcoin/blocks/index/***.ldb std::unique_ptr<CBlockTreeDB> pblocktree bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {     return Read(std::make_pair(DB_BLOCK_FILES, nFile), info); } bool CBlockTreeDB::WriteReindexing(bool fReindexing) {     if (fReindexing)         return Write(DB_REINDEX_FLAG, '1');     else         return Erase(DB_REINDEX_FLAG); } bool CBlockTreeDB::ReadReindexing(bool &fReindexing) {     fReindexing = Exists(DB_REINDEX_FLAG);     return true; } bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {     return Read(DB_LAST_BLOCK, nFile); }

比特币Coin相关数据结构

    用户的资金是由utxo构成,一个utxo相当于一个coin,  一个coin的对象是Coin。     Coin也是存储在数据库中,以<key, coin>的方式存储,key是CoinEntry(CTxout)对象Serialize化后的值 class Coin { public:     //! unspent transaction output     CTxOut out;     //! whether containing transaction was a coinbase     unsigned int fCoinBase : 1;     //! at which height this containing transaction was included in the active block chain     uint32_t nHeight : 31;     //! construct a Coin from a CTxOut and height/coinbase information.     Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}     Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}     void Clear() {         out.SetNull();         fCoinBase = false;         nHeight = 0;     }     //! empty constructor     Coin() : fCoinBase(false), nHeight(0) { }     bool IsCoinBase() const {         return fCoinBase;     }     template<typename Stream>      void Serialize(Stream &s) const {         assert(!IsSpent());         uint32_t code = nHeight * 2 + fCoinBase;         ::Serialize(s, VARINT(code));         ::Serialize(s, CTxOutCompressor(REF(out)));     }     template<typename Stream>     void Unserialize(Stream &s) {         uint32_t code = 0;         ::Unserialize(s, VARINT(code));         nHeight = code >> 1;         fCoinBase = code & 1;         ::Unserialize(s, CTxOutCompressor(out));     }     bool IsSpent() const {         return out.IsNull();     } }; struct CoinEntry {     COutPoint* outpoint;     char key;     explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN)  {}     template<typename Stream>     //key是DB_COIN+hash+n     void Serialize(Stream &s) const {         s << key;         s << outpoint->hash;         s << VARINT(outpoint->n);     }w     template<typename Stream>      void Unserialize(Stream& s) {         s >> key;         s >> outpoint->hash;         s >> VARINT(outpoint->n);     } }; }

Coin存取接口抽象类CCoinsView

    CCoinsView保存和维护所有Coin的信息,它抽象出了Coin存取和查询的接口。具体实现是CCoinsViewDB

class CCoinsView { public:     /** Retrieve the Coin (unspent transaction output) for a given outpoint.      *  Returns true only when an unspent coin was found, which is returned in coin.      *  When false is returned, coin's value is unspecified.      */     virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;     //! Just check whether a given outpoint is unspent.     virtual bool HaveCoin(const COutPoint &outpoint) const;     //! Retrieve the block hash whose state this CCoinsView currently represents     virtual uint256 GetBestBlock() const;     //! Retrieve the range of blocks that may have been only partially written.     //! If the database is in a consistent state, the result is the empty vector.     //! Otherwise, a two-element vector is returned consisting of the new and     //! the old block hash, in that order.     virtual std::vector<uint256> GetHeadBlocks() const;     //! Do a bulk modification (multiple Coin changes + BestBlock change).     //! The passed mapCoins can be modified.     virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);     //! Get a cursor to iterate over the whole state     virtual CCoinsViewCursor *Cursor() const;     //! As we use CCoinsViews polymorphically, have a virtual destructor     virtual ~CCoinsView() {}     //! Estimate database size (0 if not implemented)     virtual size_t EstimateSize() const { return 0; } }; class CCoinsViewDB final : public CCoinsView { } /** CCoinsView backed by another CCoinsView */ class CCoinsViewBacked : public CCoinsView { protected:     CCoinsView *base; public:     CCoinsViewBacked(CCoinsView *viewIn); } class CCoinsViewCache : public CCoinsViewBacked { } //CCoinsViewMemPool即可以访问coin,又可以访问coin class CCoinsViewMemPool : public CCoinsViewBacked { protected:     const CTxMemPool& mempool; public:     CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);     bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; };

Coin存取实现类CCoinsViewDB

bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {     return db.Read(CoinEntry(&outpoint), coin); } bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {     return db.Exists(CoinEntry(&outpoint)); } uint256 CCoinsViewDB::GetBestBlock() const {     uint256 hashBestChain;     if (!db.Read(DB_BEST_BLOCK, hashBestChain))         return uint256();     return hashBestChain } std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {     std::vector<uint256> vhashHeadBlocks;     if (!db.Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {         return std::vector<uint256>();     }     return vhashHeadBlocks; } CCoinsViewDB根据CoinEntry从数据库读取指定的Coin, db是key-value数据库

Coin缓存管理类CCoinsViewCache

CCoinsView之上又实现了一个Coin Cache对象: CCoinsViewCache  CCoinsViewCache是coin的cache,它通过base(CCoinsViewDB)获取Coin并缓存 CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {     CCoinsMap::iterator it = cacheCoins.find(outpoint);     if (it != cacheCoins.end())         return it;     Coin tmp;     if (!base->GetCoin(outpoint, tmp))         return cacheCoins.end();     CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;     if (ret->second.coin.IsSpent()) {         // The parent only has an empty entry for this outpoint; we can consider our         // version as fresh.         ret->second.flags = CCoinsCacheEntry::FRESH;     }     cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();     return ret; } CoinViewCache的base其实就是CCoinsViewDB bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {     // If an entry in the mempool exists, always return that one, as it's guaranteed to never     // conflict with the underlying cache, and it cannot have pruned entries (as it contains full)     // transactions. First checking the underlying cache risks returning a pruned entry instead.     CTransactionRef ptx = mempool.get(outpoint.hash);     if (ptx) {         if (outpoint.n < ptx->vout.size()) {             coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);             return true;         } else {             return false;         }     }     return base->GetCoin(outpoint, coin); }

CCoinsViewDB, CCoinsViewCache初始化

std::unique_ptr<CCoinsViewDB> pcoinsdbview; std::unique_ptr<CCoinsViewCache> pcoinsTip; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache pcoinsdbview .reset(new CCoinsViewDB( nCoinDBCache , false, fReset || fReindexChainState)); pcoinscatcher.reset (new CCoinsViewErrorCatcher( pcoinsdbview.get()) ); //CoinViewCache的base其实就是CCoinsViewDB pcoinsTip.reset(new CCoinsViewCache( pcoinscatcher.get ()));   CBlockPolicyEstimator feeEstimator; CTxMemPool mempool(&feeEstimator); CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool);

查询Coin

RPC命令行可以通过gettxout命令查询coin UniValue gettxout(const JSONRPCRequest& request) {     if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)         throw std::runtime_error(             "gettxout \"txid\" n ( include_mempool )\n"             "\nReturns details about an unspent transaction output.\n"             "\nArguments:\n"             "1. \"txid\"             (string, required) The transaction id\n"             "2. \"n\"                (numeric, required) vout number\n"             "3. \"include_mempool\"  (boolean, optional) Whether to include the mempool. Default: true."             "     Note that an unspent output that is spent in the mempool won't appear.\n"             "\nResult:\n"             "{\n"             "  \"bestblock\" : \"hash\",    (string) the block hash\n"             "  \"confirmations\" : n,       (numeric) The number of confirmations\n"             "  \"value\" : x.xxx,           (numeric) The transaction value in " + CURRENCY_UNIT + "\n"             "  \"scriptPubKey\" : {         (json object)\n"             "     \"asm\" : \"code\",       (string) \n"             "     \"hex\" : \"hex\",        (string) \n"             "     \"reqSigs\" : n,          (numeric) Number of required signatures\n"             "     \"type\" : \"pubkeyhash\", (string) The type, eg pubkeyhash\n"             "     \"addresses\" : [          (array of string) array of bitcoin addresses\n"             "        \"address\"     (string) bitcoin address\n"             "        ,...\n"             "     ]\n"             "  },\n"             "  \"coinbase\" : true|false   (boolean) Coinbase or not\n"             "}\n"             "\nExamples:\n"             "\nGet unspent transactions\n"             + HelpExampleCli("listunspent", "") +             "\nView the details\n"             + HelpExampleCli("gettxout", "\"txid\" 1") +             "\nAs a json rpc call\n"             + HelpExampleRpc("gettxout", "\"txid\", 1")         );     LOCK(cs_main);     UniValue ret(UniValue::VOBJ);     std::string strHash = request.params[0].get_str();     //txid     uint256 hash(uint256S(strHash));     //index     int n = request.params[1].get_int();     COutPoint out(hash, n);     bool fMempool = true;     if (!request.params[2].isNull())         fMempool = request.params[2].get_bool();     Coin coin;     if (fMempool) {         LOCK(mempool.cs);         CCoinsViewMemPool view(pcoinsTip.get(), mempool);         if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {             return NullUniValue;         }     } else {         if (!pcoinsTip->GetCoin(out, coin)) {             return NullUniValue;         }     }     const CBlockIndex* pindex = LookupBlockIndex(pcoinsTip->GetBestBlock());     ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());     if (coin.nHeight == MEMPOOL_HEIGHT) {         ret.pushKV("confirmations", 0);     } else {         ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1));     }     ret.pushKV("value", ValueFromAmount(coin.out.nValue));     UniValue o(UniValue::VOBJ);     ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);     ret.pushKV("scriptPubKey", o);     ret.pushKV("coinbase", (bool)coin.fCoinBase);     return ret; }

Coin对象的生成

    节点收到新区块数据时会调用ConnectBlock,然后就会添加区块里的交易中的Coin         ConnectBlock->UpdateCoins->AddCoins void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check) {     bool fCoinbase = tx.IsCoinBase();     const uint256& txid = tx.GetHash();     for (size_t i = 0; i < tx.vout.size(); ++i) {         bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;         // Always set the possible_overwrite flag to AddCoin for coinbase txn, in order to correctly         // deal with the pre-BIP30 occurrences of duplicate coinbase transactions.         cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);     } } FlushStateToDisk->cache.flush bool CCoinsViewCache::Flush() {     bool fOk = base->BatchWrite(cacheCoins, hashBlock);     cacheCoins.clear();     cachedCoinsUsage = 0;     return fOk; }
转载请注明原文地址: https://www.6miu.com/read-2625865.html

最新回复(0)