$ bitcoin-cli help getblock
getblock "hash" ( verbose )
如果 verbose 是 false,返回一个序列化的字符串,区块 'hash' 为 16 进制编码的数据。
如果 verbose 是 true,返回一个区块 相关信息的对象。
参数:
1. "hash"(字符串,必备)区块哈希
2. verbose(布尔型,可选,默认为 true)true 对应一个 json 对象,false 对应 16 进制编码的数据
结果(对 verbose 为 true):
{
"hash" : "hash", (字符串)区块哈希(和提供的一样)
"confirmations" : n, (数字)确认数,若区块不在主链上则为 -1
"size" : n, (数字)区块大小
"height" : n, (数字)区块高度或索引
"version" : n, (数字)区块版本
"merkleroot" : "xxxx", (字符串)默克尔树根
"tx" : [ (字符串数组)交易索引集
"transactionid" (字符串)交易索引
,...
],
"time" : ttt, (数字)从格林尼治时间(1970-01-01 00:00:00)开始以秒为单位的区块时间
"mediantime" : ttt, (数字)从格林尼治时间(1970-01-01 00:00:00)开始以秒为单位的中间区块时间
"nonce" : n, (数字)随机数
"bits" : "1d00ffff", (字符串)难度对应值
"difficulty" : x.xxx, (数字)难度
"chainwork" : "xxxx", (字符串)预计产生该区块上链需要的哈希次数(16 进制)
"previousblockhash" : "hash",(字符串)上一个区块的哈希
"nextblockhash" : "hash" (字符串)下一个区块的哈希
}
结果(对 verbose 为 false):
"data"(字符串)一个序列化的字符串,区块 'hash' 的 16 进制编码的数据。
例子:
> bitcoin-cli getblock 00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09
> curl --user myusername:mypassword --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getblock", "params": ["00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/
</pre>
## 源码剖析
`getblock` 对应的函数在文件 `rpcserver.h` 中被引用。
```cpp
extern UniValue getblock(const UniValue& params, bool fHelp);
```
实现在文件 `rpcblockchain.cpp` 中。
```cpp
UniValue getblock(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
"getblock \"hash\" ( verbose )\n"
"\nIf verbose is false, returns a string that is serialized, hex-encoded data for block 'hash'.\n"
"If verbose is true, returns an Object with information about block .\n"
"\nArguments:\n"
"1. \"hash\" (string, required) The block hash\n"
"2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n"
"\nResult (for verbose = true):\n"
"{\n"
" \"hash\" : \"hash\", (string) the block hash (same as provided)\n"
" \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n"
" \"size\" : n, (numeric) The block size\n"
" \"height\" : n, (numeric) The block height or index\n"
" \"version\" : n, (numeric) The block version\n"
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
" \"tx\" : [ (array of string) The transaction ids\n"
" \"transactionid\" (string) The transaction id\n"
" ,...\n"
" ],\n"
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
" \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n"
" \"nonce\" : n, (numeric) The nonce\n"
" \"bits\" : \"1d00ffff\", (string) The bits\n"
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
" \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n"
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
"}\n"
"\nResult (for verbose=false):\n"
"\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n"
"\nExamples:\n"
+ HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
+ HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
); // 1. 帮助内容
LOCK(cs_main);
std::string strHash = params[0].get_str();
uint256 hash(uint256S(strHash));
bool fVerbose = true;
if (params.size() > 1)
fVerbose = params[1].get_bool();
if (mapBlockIndex.count(hash) == 0) // 2. 检查区块是否存在
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
CBlock block;
CBlockIndex* pblockindex = mapBlockIndex[hash];
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) // 3. 检查区块数据是否完整
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)");
if(!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) // 4. 从磁盘上读取区块
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
if (!fVerbose) // 5. 序列化区块并返回
{
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << block;
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end());
return strHex;
}
return blockToJSON(block, pblockindex); // 6. 打包区块信息为 JSON 格式并返回
}
```
### 1. 帮助内容
参考[比特币 RPC 命令「getbestblockhash」1. 帮助内容](/blog/2018/05/bitcoin-rpc-getbestblockhash.html#1-帮助内容)。
### 2. 检查区块是否存在
区块索引映射对象 `mapBlockIndex` 在文件 `main.h` 中被引用。
```cpp
struct BlockHasher
{
size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); }
};
...
typedef boost::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap;
extern BlockMap mapBlockIndex;
```
定义在文件 `main.cpp` 中,是一个 `boost::unordered_map`。
这里的区块哈希器 `BlockHasher` 是一个重载了函数调用运算符的函数对象,用于获取区块哈希对象 `uint256` 的引用。
```cpp
BlockMap mapBlockIndex;
```
### 3. 检查区块数据是否完整
已修剪标志 `fHavePruned` 在文件 `main.h` 中被引用。
```cpp
/** True if any block files have ever been pruned. */
extern bool fHavePruned; // 如果任何区块文件被修剪过则为 true。
```
定义在文件 `main.cpp` 中,默认为 `false`,表示区块未修剪。
```cpp
bool fHavePruned = false;
```
区块索引变量 `pblockindex->nStatus` 和 `pblockindex->nTx` 定义在文件 `chain.h` 的区块索引类 `CBlockIndex` 中。
`BLOCK_HAVE_DATA` 是一个枚举类型,值为 `8`,表示在区块文件中是完整的区块。
```cpp
enum BlockStatus {
...
BLOCK_HAVE_DATA = 8, //! full block available in blk*.dat
...
};
/** 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:
...
//! Number of transactions in this block.
//! Note: in a potential headers-first mode, this number cannot be relied upon
unsigned int nTx; //! 该区块中的交易数。
...
//! Verification status of this block. See enum BlockStatus
unsigned int nStatus; //! 该区块的验证状态。查看 enum BlockStatus
...
};
```
### 4. 从磁盘上读取区块
从磁盘上读取区块函数 `ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())` 声明在文件 `main.h` 中。
```cpp
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams);
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams);
```
实现在文件 `main.cpp` 中。
```cpp
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams)
{
block.SetNull();
// Open history file to read
CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull())
return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString());
// Read block
try {
filein >> block;
}
catch (const std::exception& e) {
return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString());
}
// Check the header
if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
return true;
}
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
{
if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams)) // 调用上面的重载函数读取区块
return false;
if (block.GetHash() != pindex->GetBlockHash()) // 验证读取的区块哈希
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
pindex->ToString(), pindex->GetBlockPos().ToString());
return true;
}
```
函数 `block.GetHash()` 声明在文件 `primitives/block.h` 的区块头类 `CBlockHeader` 中。
```cpp
/** 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.
*/
class CBlockHeader // 区块头部分(包含区块版本、前一个区块的哈希、默尔克树根哈希、创建区块的时间、难度对应值和随机数)共 80 bytes
{
public:
...
uint256 GetHash() const;
...
};
class CBlock : public CBlockHeader
{
...
};
```
实现在文件 `primitives/block.cpp` 中。
```cpp
uint256 CBlockHeader::GetHash() const
{
return SerializeHash(*this);
}
```
这里调用模板函数 `SerializeHash(*this)` 序列化区块的哈希。
### 5. 序列化区块并返回
若 `verbose` 为 `false`,则序列化区块数据,转换为 16 进制并返回。
数据流类 `CDataStream` 定义在文件 `streams.h` 中,重载了输出运算符 `<<`。
```cpp
/** Double ended buffer combining vector and stream-like interfaces.
*
* >> and << read and write unformatted data using the above serialization templates.
* Fills with data in linear time; some stringstream implementations take N^2 time.
*/
class CDataStream
{
...
template
CDataStream& operator<<(const T& obj)
{
// Serialize to this stream
::Serialize(*this, obj, nType, nVersion);
return (*this);
}
...
};
```
结合向量和流式接口的双端缓冲区。
使用上述序列化模板 `>>` 和 `<<` 来读写未格式化的数据。
以线性时间填充数据;一些字符串流的实现需要 N^2 的时间。
调用模板函数 `HexStr(ssBlock.begin(), ssBlock.end())` 把流化的数据转换为 16 进制。
实现在文件 `utilstrencodings.h` 中。
```cpp
template
std::string HexStr(const T itbegin, const T itend, bool fSpaces=false)
{
std::string rv;
static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
rv.reserve((itend-itbegin)*3); // 乘 3 的原因:一个字节 8 位,对应 16 进制的 2 位,再加上中间的空格
for(T it = itbegin; it < itend; ++it)
{
unsigned char val = (unsigned char)(*it);
if(fSpaces && it != itbegin) // 空格隔开每一个 16 进制字符,默认不加空格
rv.push_back(' ');
rv.push_back(hexmap[val>>4]); // 高 4 位
rv.push_back(hexmap[val&15]); // 低 4 位
}
return rv;
}
```
把每个字节拆成高 4 位和低 4 位分别转换为 16 进制的字符。
还可以在每个 16 进制字符中间加入空格,默认不加。
### 6. 打包区块信息为 JSON 格式并返回
若 verbose 为 true,则返回区块信息。
函数 `blockToJSON(block, pblockindex)` 实现在文件 `rpcblockchain.cpp` 中。
```cpp
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false)
{
UniValue result(UniValue::VOBJ);
result.push_back(Pair("hash", block.GetHash().GetHex())); // 先加入区块的哈希(16 进制形式)
int confirmations = -1; // 记录该区块的确认数
// Only report confirmations if the block is on the main chain
if (chainActive.Contains(blockindex)) // 若该区块在链上
confirmations = chainActive.Height() - blockindex->nHeight + 1; // 计算确认数,注:刚上链的确认数为 1
result.push_back(Pair("confirmations", confirmations)); // 加入确认数
result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); // 区块大小(单位字节)
result.push_back(Pair("height", blockindex->nHeight)); // 区块高度
result.push_back(Pair("version", block.nVersion)); // 区块版本
result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex())); // 默克树根
UniValue txs(UniValue::VARR); // 数组类型的交易对象
BOOST_FOREACH(const CTransaction&tx, block.vtx)
{ // 遍历交易列表
if(txDetails) // false
{ // 交易细节
UniValue objTx(UniValue::VOBJ);
TxToJSON(tx, uint256(), objTx); // 把交易信息转换为 JSON 格式输入到 objTx
txs.push_back(objTx);
}
else // 加入交易哈希
txs.push_back(tx.GetHash().GetHex());
}
result.push_back(Pair("tx", txs)); // 交易集
result.push_back(Pair("time", block.GetBlockTime())); // 获取区块创建时间
result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast()));
result.push_back(Pair("nonce", (uint64_t)block.nNonce)); // 随机数
result.push_back(Pair("bits", strprintf("%08x", block.nBits))); // 难度对应值
result.push_back(Pair("difficulty", GetDifficulty(blockindex))); // 难度
result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); // 链工作量
if (blockindex->pprev) // 如果有前一个区块
result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); // 加入前一个区块的哈希
CBlockIndex *pnext = chainActive.Next(blockindex);
if (pnext) // 如果后后一个区块
result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); // 加入后一个区块的哈希
return result;
}
```
函数 `chainActive.Contains(blockindex)` 和 `chainActive.Next(blockindex)` 均声明在文件 `chain.h` 的链类 `CChain` 中。
```cpp
/** An in-memory indexed chain of blocks. */
class CChain { // 一个内存中已索引的区块链。
...
/** Returns the index entry at a particular height in this chain, or NULL if no such height exists. */
CBlockIndex *operator[](int nHeight) const { // 返回该链上指定高度的索引条目,若这样的高度不存在则返回 NULL。
if (nHeight < 0 || nHeight >= (int)vChain.size())
return NULL;
return vChain[nHeight];
}
...
/** Efficiently check whether a block is present in this chain. */
bool Contains(const CBlockIndex *pindex) const { // 有效检测该链上是否存在某个块。
return (*this)[pindex->nHeight] == pindex;
}
/** Find the successor of a block in this chain, or NULL if the given index is not found or is the tip. */
CBlockIndex *Next(const CBlockIndex *pindex) const { // 在该链上找到一个区块的后继者,若给定的区块未找到或为链尖则返回 NULL。
if (Contains(pindex))
return (*this)[pindex->nHeight + 1];
else
return NULL;
}
...
};
```
## 参考链接
* [bitcoin/rpcserver.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/rpcserver.h){:target="_blank"}
* [bitcoin/rpcserver.cpp at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/rpcserver.cpp){:target="_blank"}
* [bitcoin/rpcblockchain.cpp at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/rpcblockchain.cpp){:target="_blank"}
* [bitcoin/main.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/main.h){:target="_blank"}
* [bitcoin/main.cpp at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/main.cpp){:target="_blank"}
* [bitcoin/chain.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/chain.h){:target="_blank"}
* [bitcoin/block.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/primitives/block.h){:target="_blank"}
* [bitcoin/block.cpp at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/primitives/block.cpp){:target="_blank"}
* [bitcoin/streams.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/streams.h){:target="_blank"}
* [bitcoin/utilstrencodings.h at v0.12.1 · bitcoin/bitcoin](https://github.com/bitcoin/bitcoin/blob/v0.12.1/src/utilstrencodings.h){:target="_blank"}