比特币源码情景分析之区块数据结构
磁盘中区块(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;
}