区块链之所以称做区块链,其实就是一个类似链表的东西,新区块通过记载父区块的哈希,以此来不断的迭代形成一条链状的数据结构。先看一下区块的数据结构:
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树部分主要代码在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