这篇文章主要分析rpc模块代码的一个整体逻辑,详细的代码讲解,请关注下一篇文章
在这里,我们暂时先抛开bitcoin代码,仅仅来谈RPC,提到RPC大家肯定首先会想到远程过程服务调用,既然是调用,那就肯定存在一个client端和一个server端,clent端与server端通过RPC这个黑盒通过http请求进行交互,那么就有一个问题,我自定义的json格式的字符串(这里拿json来进行举例)是无法在网络上流通的,所以,必然会涉及到一个json的序列化与反序列化的过程。综上所述,大致流程如下:
RPC详解
rpc命令的入口函数是从 bitcoin-abc/src/rpc/register.h
出发的,根据功能模块的不同,分了如下函数用来注册rpc命令:
class CRPCTable;
//区块链RPC命令注册
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
//P2P网络RPC命令注册
void RegisterNetRPCCommands(CRPCTable &tableRPC);
//其他工具RPC命令注册
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
//挖矿RPC命令注册
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
//交易PRC命令注册
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
//BCH特有的RPC命令注册
void RegisterABCRPCCommands(CRPCTable &tableRPC);
首先来看 CRPCTable
这个类,它是一个调度表变量,专门用来存储RPC命令,它有一个很重要的方法,如下:
bool appendCommand(const std::string &name, const CRPCCommand *pcmd);
其他模块的rpc命令通过这个函数不断追加到rpcTable中。有两个参数,一个是rpc命令的名字name,另一个是指向rpc command的一个指针。
接下来我们看一下 CRPCCommand
这个类,它主要有如下定义:
std::string category;
std::string name;
rpcfn_type actor;
bool okSafeMode;
std::vector argNames;
它表示了当我需要新增加一个rpc命令时,我这个新增加的命令需要描述如上所述的信息。
下面,我们一起探索一下,各个模块都是怎么注册自己的rpc命令的,先看blockchain,具体如下:
static const CRPCCommand commands[] = {
// category类别 name actor (function) okSafe argNames
// ------------------- ------------------------ ---------------------- ------ ----------
{ "blockchain", "getblockchaininfo", getblockchaininfo, true, {} },
{ "blockchain", "getbestblockhash", getbestblockhash, true, {} },
{ "blockchain", "getblockcount", getblockcount, true, {} },
{ "blockchain", "getblock", getblock, true, {"blockhash","verbose"} },
{ "blockchain", "getblockhash", getblockhash, true, {"height"} },
{ "blockchain", "getblockheader", getblockheader, true, {"blockhash","verbose"} },
};
篇幅有限,只列举其中几个。区块链rpc命令在常量数组commands中存储,name就是我们在client中可以使用的rpc命令,那么他是如何注册到CRPCTable中的呢?
void RegisterBlockchainRPCCommands(CRPCTable &t) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
如上,使用for循环不断迭代commands中的元素,通过appendCommand方法把命令追加到rpcTable中。
其他模块注册的方式与此类似,都是调用appendCommand方法进行追加,故不在赘述。
注册完成之后,当我们调用具体的某一个rpc命令时,会发生什么呢?我们拿blockchain中的getblockchaininfo来举例,具体实现如下:
UniValue getblockchaininfo(const Config &config, const JSONRPCRequest &request){
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error("...")
}
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("chain", Params().NetworkIDString()));
obj.push_back(Pair("blocks", int(chainActive.Height())));
obj.push_back(
Pair("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1));
obj.push_back(
Pair("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex()));
obj.push_back(Pair("difficulty", double(GetDifficulty(chainActive.Tip()))));
obj.push_back(
Pair("mediantime", int64_t(chainActive.Tip()->GetMedianTimePast())));
obj.push_back(
Pair("verificationprogress",
GuessVerificationProgress(Params().TxData(), chainActive.Tip())));
obj.push_back(Pair("chainwork", chainActive.Tip()->nChainWork.GetHex()));
obj.push_back(Pair("pruned", fPruneMode));
const Consensus::Params &consensusParams = Params().GetConsensus();
CBlockIndex *tip = chainActive.Tip();
UniValue softforks(UniValue::VARR);
UniValue bip9_softforks(UniValue::VOBJ);
softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams));
softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams));
softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams));
BIP9SoftForkDescPushBack(bip9_softforks, "csv", consensusParams,
Consensus::DEPLOYMENT_CSV);
obj.push_back(Pair("softforks", softforks));
obj.push_back(Pair("bip9_softforks", bip9_softforks));
if (fPruneMode) {
CBlockIndex *block = chainActive.Tip();
while (block && block->pprev &&
(block->pprev->nStatus & BLOCK_HAVE_DATA)) {
block = block->pprev;
}
obj.push_back(Pair("pruneheight", block->nHeight));
}
return obj;
如上,每一个rpc命令在他们相应的模块中,都有基于这个rpc所表述含义的相应代码实现。
我们看到getblockchaininfo这个函数返回值是UniValue,UniValue是cpp中一个json解析库,用来将json格式的rpc命令转化为网络可识别的。转化实现主要在client.cpp
中进行了简单的包装,具体实现如下:
class CRPCConvertParam {
public:
std::string methodName; //!< method whose params want conversion
int paramIdx; //!< 0-based idx of param to convert
std::string paramName; //!< parameter name
};
- methodName 代表的是 rpc 命令的 name
- paramIDx 代表的是参数的位置,从 0 开始算起,
- paramName 描述的是参数的具体信息,也就是前面methodName所代表的含义
我们会看到有时候会有相同的methodName,但是他们的paramIDx不一样,所代表的描述信息也就不同,这里要注意区分
clint.cpp
主要是服务于 bitcoin-cli
客户端在这边将json的命令进行序列化之后,发送给对应的服务端(各个不同的模块),服务端在做相应的处理。
在protocol.h中,我们定义了一些HTTP的状态码,以及RPC的错误码,json的格式规范遵循的是RFC4627。
//! HTTP status codes
enum HTTPStatusCode {
HTTP_OK = 200,
HTTP_BAD_REQUEST = 400,
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_BAD_METHOD = 405,
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_SERVICE_UNAVAILABLE = 503,
};
//! Bitcoin RPC error codes
enum RPCErrorCode {
//! Standard JSON-RPC 2.0 errors
// RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400).
// It should not be used for application-layer errors.
RPC_INVALID_REQUEST = -32600,
// RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404).
// It should not be used for application-layer errors.
RPC_METHOD_NOT_FOUND = -32601,
RPC_INVALID_PARAMS = -32602,
.....
}
本文由Copernicus 团队 冉小龙 分析编写,转载无需授权!
家境清寒,整理不易。