比特币源码解析:RPC详解

匿名 (未验证) 提交于 2019-12-03 00:19:01

比特币源码解析:RPC详解

这篇文章主要分析rpc模块代码的一个整体逻辑,详细的代码讲解,请关注下一篇文章

在这里,我们暂时先抛开bitcoin代码,仅仅来谈RPC,提到RPC大家肯定首先会想到远程过程服务调用,既然是调用,那就肯定存在一个client端和一个server端,clent端与server端通过RPC这个黑盒通过http请求进行交互,那么就有一个问题,我自定义的json格式的字符串(这里拿json来进行举例)是无法在网络上流通的,所以,必然会涉及到一个json的序列化与反序列化的过程。综上所述,大致流程如下:

image.png

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<std::string> 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,     ..... } 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!