Initial Vexta Core v0.1.0 release
This commit is contained in:
commit
25c454329d
2481 changed files with 689289 additions and 0 deletions
2747
src/rpc/blockchain.cpp
Normal file
2747
src/rpc/blockchain.cpp
Normal file
File diff suppressed because it is too large
Load diff
74
src/rpc/blockchain.h
Normal file
74
src/rpc/blockchain.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_BLOCKCHAIN_H
|
||||
#define DIGIBYTE_RPC_BLOCKCHAIN_H
|
||||
|
||||
#include <amount.h>
|
||||
#include <core_io.h>
|
||||
#include <streams.h>
|
||||
#include <sync.h>
|
||||
|
||||
#include <any>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
extern RecursiveMutex cs_main;
|
||||
|
||||
class CBlock;
|
||||
class CBlockIndex;
|
||||
class CBlockPolicyEstimator;
|
||||
class CChainState;
|
||||
class CTxMemPool;
|
||||
class ChainstateManager;
|
||||
class UniValue;
|
||||
struct NodeContext;
|
||||
|
||||
static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5;
|
||||
|
||||
/**
|
||||
* Get the difficulty of the net wrt to the given block index.
|
||||
*
|
||||
* @return A floating point number that is a multiple of the main net minimum
|
||||
* difficulty (4295032833 hashes).
|
||||
*/
|
||||
double GetDifficulty(const CBlockIndex* tip = NULL, const CBlockIndex* blockindex = nullptr);
|
||||
|
||||
/** Callback for when block tip changed. */
|
||||
void RPCNotifyBlockChange(const CBlockIndex*);
|
||||
|
||||
/** Block description to JSON */
|
||||
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false) LOCKS_EXCLUDED(cs_main);
|
||||
|
||||
/** Mempool information to JSON */
|
||||
UniValue MempoolInfoToJSON(const CTxMemPool& pool);
|
||||
|
||||
/** Mempool to JSON */
|
||||
UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false);
|
||||
|
||||
/** Block header to JSON */
|
||||
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main);
|
||||
|
||||
/** Used by getblockstats to get feerates at different percentiles by weight */
|
||||
void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight);
|
||||
|
||||
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
|
||||
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr);
|
||||
|
||||
NodeContext& EnsureAnyNodeContext(const std::any& context);
|
||||
CTxMemPool& EnsureMemPool(const NodeContext& node);
|
||||
CTxMemPool& EnsureAnyMemPool(const std::any& context);
|
||||
ChainstateManager& EnsureChainman(const NodeContext& node);
|
||||
ChainstateManager& EnsureAnyChainman(const std::any& context);
|
||||
CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node);
|
||||
CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context);
|
||||
|
||||
/**
|
||||
* Helper to create UTXO snapshots given a chainstate and a file handle.
|
||||
* @return a UniValue map containing metadata about the snapshot.
|
||||
*/
|
||||
UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile);
|
||||
|
||||
#endif
|
||||
279
src/rpc/client.cpp
Normal file
279
src/rpc/client.cpp
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <rpc/client.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* Specify a (method, idx, name) here if the argument is a non-string RPC
|
||||
* argument and needs to be converted from JSON.
|
||||
*
|
||||
* @note Parameter indexes start from 0.
|
||||
*/
|
||||
static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{
|
||||
{ "setmocktime", 0, "timestamp" },
|
||||
{ "mockscheduler", 0, "delta_time" },
|
||||
{ "utxoupdatepsbt", 1, "descriptors" },
|
||||
{ "generatetoaddress", 0, "nblocks" },
|
||||
{ "generatetoaddress", 2, "maxtries" },
|
||||
{ "generatetodescriptor", 0, "num_blocks" },
|
||||
{ "generatetodescriptor", 2, "maxtries" },
|
||||
{ "generateblock", 1, "transactions" },
|
||||
{ "getnetworkhashps", 0, "nblocks" },
|
||||
{ "getnetworkhashps", 1, "height" },
|
||||
{ "sendtoaddress", 1, "amount" },
|
||||
{ "sendtoaddress", 4, "subtractfeefromamount" },
|
||||
{ "sendtoaddress", 5 , "replaceable" },
|
||||
{ "sendtoaddress", 6 , "conf_target" },
|
||||
{ "sendtoaddress", 8, "avoid_reuse" },
|
||||
{ "sendtoaddress", 9, "fee_rate"},
|
||||
{ "sendtoaddress", 10, "verbose"},
|
||||
{ "settxfee", 0, "amount" },
|
||||
{ "sethdseed", 0, "newkeypool" },
|
||||
{ "getreceivedbyaddress", 1, "minconf" },
|
||||
{ "getreceivedbylabel", 1, "minconf" },
|
||||
{ "listreceivedbyaddress", 0, "minconf" },
|
||||
{ "listreceivedbyaddress", 1, "include_empty" },
|
||||
{ "listreceivedbyaddress", 2, "include_watchonly" },
|
||||
{ "listreceivedbylabel", 0, "minconf" },
|
||||
{ "listreceivedbylabel", 1, "include_empty" },
|
||||
{ "listreceivedbylabel", 2, "include_watchonly" },
|
||||
{ "getbalance", 1, "minconf" },
|
||||
{ "getbalance", 2, "include_watchonly" },
|
||||
{ "getbalance", 3, "avoid_reuse" },
|
||||
{ "getblockhash", 0, "height" },
|
||||
{ "waitforblockheight", 0, "height" },
|
||||
{ "waitforblockheight", 1, "timeout" },
|
||||
{ "waitforblock", 1, "timeout" },
|
||||
{ "waitfornewblock", 0, "timeout" },
|
||||
{ "listtransactions", 1, "count" },
|
||||
{ "listtransactions", 2, "skip" },
|
||||
{ "listtransactions", 3, "include_watchonly" },
|
||||
{ "walletpassphrase", 1, "timeout" },
|
||||
{ "getblocktemplate", 0, "template_request" },
|
||||
{ "listsinceblock", 1, "target_confirmations" },
|
||||
{ "listsinceblock", 2, "include_watchonly" },
|
||||
{ "listsinceblock", 3, "include_removed" },
|
||||
{ "sendmany", 1, "amounts" },
|
||||
{ "sendmany", 2, "minconf" },
|
||||
{ "sendmany", 4, "subtractfeefrom" },
|
||||
{ "sendmany", 5 , "replaceable" },
|
||||
{ "sendmany", 6 , "conf_target" },
|
||||
{ "sendmany", 8, "fee_rate"},
|
||||
{ "sendmany", 9, "verbose" },
|
||||
{ "deriveaddresses", 1, "range" },
|
||||
{ "scantxoutset", 1, "scanobjects" },
|
||||
{ "addmultisigaddress", 0, "nrequired" },
|
||||
{ "addmultisigaddress", 1, "keys" },
|
||||
{ "createmultisig", 0, "nrequired" },
|
||||
{ "createmultisig", 1, "keys" },
|
||||
{ "listunspent", 0, "minconf" },
|
||||
{ "listunspent", 1, "maxconf" },
|
||||
{ "listunspent", 2, "addresses" },
|
||||
{ "listunspent", 3, "include_unsafe" },
|
||||
{ "listunspent", 4, "query_options" },
|
||||
{ "getblock", 1, "verbosity" },
|
||||
{ "getblock", 1, "verbose" },
|
||||
{ "getblockheader", 1, "verbose" },
|
||||
{ "getchaintxstats", 0, "nblocks" },
|
||||
{ "gettransaction", 1, "include_watchonly" },
|
||||
{ "gettransaction", 2, "verbose" },
|
||||
{ "getrawtransaction", 1, "verbose" },
|
||||
{ "createrawtransaction", 0, "inputs" },
|
||||
{ "createrawtransaction", 1, "outputs" },
|
||||
{ "createrawtransaction", 2, "locktime" },
|
||||
{ "createrawtransaction", 3, "replaceable" },
|
||||
{ "decoderawtransaction", 1, "iswitness" },
|
||||
{ "signrawtransactionwithkey", 1, "privkeys" },
|
||||
{ "signrawtransactionwithkey", 2, "prevtxs" },
|
||||
{ "signrawtransactionwithwallet", 1, "prevtxs" },
|
||||
{ "sendrawtransaction", 1, "maxfeerate" },
|
||||
{ "testmempoolaccept", 0, "rawtxs" },
|
||||
{ "testmempoolaccept", 1, "maxfeerate" },
|
||||
{ "combinerawtransaction", 0, "txs" },
|
||||
{ "fundrawtransaction", 1, "options" },
|
||||
{ "fundrawtransaction", 2, "iswitness" },
|
||||
{ "walletcreatefundedpsbt", 0, "inputs" },
|
||||
{ "walletcreatefundedpsbt", 1, "outputs" },
|
||||
{ "walletcreatefundedpsbt", 2, "locktime" },
|
||||
{ "walletcreatefundedpsbt", 3, "options" },
|
||||
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
||||
{ "walletprocesspsbt", 1, "sign" },
|
||||
{ "walletprocesspsbt", 3, "bip32derivs" },
|
||||
{ "createpsbt", 0, "inputs" },
|
||||
{ "createpsbt", 1, "outputs" },
|
||||
{ "createpsbt", 2, "locktime" },
|
||||
{ "createpsbt", 3, "replaceable" },
|
||||
{ "combinepsbt", 0, "txs"},
|
||||
{ "joinpsbts", 0, "txs"},
|
||||
{ "finalizepsbt", 1, "extract"},
|
||||
{ "converttopsbt", 1, "permitsigdata"},
|
||||
{ "converttopsbt", 2, "iswitness"},
|
||||
{ "gettxout", 1, "n" },
|
||||
{ "gettxout", 2, "include_mempool" },
|
||||
{ "gettxoutproof", 0, "txids" },
|
||||
{ "gettxoutsetinfo", 1, "hash_or_height" },
|
||||
{ "gettxoutsetinfo", 2, "use_index"},
|
||||
{ "lockunspent", 0, "unlock" },
|
||||
{ "lockunspent", 1, "transactions" },
|
||||
{ "send", 0, "outputs" },
|
||||
{ "send", 1, "conf_target" },
|
||||
{ "send", 3, "fee_rate"},
|
||||
{ "send", 4, "options" },
|
||||
{ "importprivkey", 2, "rescan" },
|
||||
{ "importaddress", 2, "rescan" },
|
||||
{ "importaddress", 3, "p2sh" },
|
||||
{ "importpubkey", 2, "rescan" },
|
||||
{ "importmulti", 0, "requests" },
|
||||
{ "importmulti", 1, "options" },
|
||||
{ "importdescriptors", 0, "requests" },
|
||||
{ "verifychain", 0, "checklevel" },
|
||||
{ "verifychain", 1, "nblocks" },
|
||||
{ "getblockstats", 0, "hash_or_height" },
|
||||
{ "getblockstats", 1, "stats" },
|
||||
{ "pruneblockchain", 0, "height" },
|
||||
{ "keypoolrefill", 0, "newsize" },
|
||||
{ "getrawmempool", 0, "verbose" },
|
||||
{ "getrawmempool", 1, "mempool_sequence" },
|
||||
{ "estimatesmartfee", 0, "conf_target" },
|
||||
{ "estimaterawfee", 0, "conf_target" },
|
||||
{ "estimaterawfee", 1, "threshold" },
|
||||
{ "prioritisetransaction", 1, "dummy" },
|
||||
{ "prioritisetransaction", 2, "fee_delta" },
|
||||
{ "setban", 2, "bantime" },
|
||||
{ "setban", 3, "absolute" },
|
||||
{ "setnetworkactive", 0, "state" },
|
||||
{ "setwalletflag", 1, "value" },
|
||||
{ "getmempoolancestors", 1, "verbose" },
|
||||
{ "getmempooldescendants", 1, "verbose" },
|
||||
{ "bumpfee", 1, "options" },
|
||||
{ "psbtbumpfee", 1, "options" },
|
||||
{ "logging", 0, "include" },
|
||||
{ "logging", 1, "exclude" },
|
||||
{ "disconnectnode", 1, "nodeid" },
|
||||
{ "upgradewallet", 0, "version" },
|
||||
// Echo with conversion (For testing only)
|
||||
{ "echojson", 0, "arg0" },
|
||||
{ "echojson", 1, "arg1" },
|
||||
{ "echojson", 2, "arg2" },
|
||||
{ "echojson", 3, "arg3" },
|
||||
{ "echojson", 4, "arg4" },
|
||||
{ "echojson", 5, "arg5" },
|
||||
{ "echojson", 6, "arg6" },
|
||||
{ "echojson", 7, "arg7" },
|
||||
{ "echojson", 8, "arg8" },
|
||||
{ "echojson", 9, "arg9" },
|
||||
{ "rescanblockchain", 0, "start_height"},
|
||||
{ "rescanblockchain", 1, "stop_height"},
|
||||
{ "createwallet", 1, "disable_private_keys"},
|
||||
{ "createwallet", 2, "blank"},
|
||||
{ "createwallet", 4, "avoid_reuse"},
|
||||
{ "createwallet", 5, "descriptors"},
|
||||
{ "createwallet", 6, "load_on_startup"},
|
||||
{ "createwallet", 7, "external_signer"},
|
||||
{ "loadwallet", 1, "load_on_startup"},
|
||||
{ "unloadwallet", 1, "load_on_startup"},
|
||||
{ "getnodeaddresses", 0, "count"},
|
||||
{ "addpeeraddress", 1, "port"},
|
||||
{ "stop", 0, "wait" },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
class CRPCConvertTable
|
||||
{
|
||||
private:
|
||||
std::set<std::pair<std::string, int>> members;
|
||||
std::set<std::pair<std::string, std::string>> membersByName;
|
||||
|
||||
public:
|
||||
CRPCConvertTable();
|
||||
|
||||
bool convert(const std::string& method, int idx) {
|
||||
return (members.count(std::make_pair(method, idx)) > 0);
|
||||
}
|
||||
bool convert(const std::string& method, const std::string& name) {
|
||||
return (membersByName.count(std::make_pair(method, name)) > 0);
|
||||
}
|
||||
};
|
||||
|
||||
CRPCConvertTable::CRPCConvertTable()
|
||||
{
|
||||
for (const auto& cp : vRPCConvertParams) {
|
||||
members.emplace(cp.methodName, cp.paramIdx);
|
||||
membersByName.emplace(cp.methodName, cp.paramName);
|
||||
}
|
||||
}
|
||||
|
||||
static CRPCConvertTable rpcCvtTable;
|
||||
|
||||
/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
|
||||
* as well as objects and arrays.
|
||||
*/
|
||||
UniValue ParseNonRFCJSONValue(const std::string& strVal)
|
||||
{
|
||||
UniValue jVal;
|
||||
if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
|
||||
!jVal.isArray() || jVal.size()!=1)
|
||||
throw std::runtime_error(std::string("Error parsing JSON: ") + strVal);
|
||||
return jVal[0];
|
||||
}
|
||||
|
||||
UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams)
|
||||
{
|
||||
UniValue params(UniValue::VARR);
|
||||
|
||||
for (unsigned int idx = 0; idx < strParams.size(); idx++) {
|
||||
const std::string& strVal = strParams[idx];
|
||||
|
||||
if (!rpcCvtTable.convert(strMethod, idx)) {
|
||||
// insert string value directly
|
||||
params.push_back(strVal);
|
||||
} else {
|
||||
// parse string as JSON, insert bool/number/object/etc. value
|
||||
params.push_back(ParseNonRFCJSONValue(strVal));
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
|
||||
{
|
||||
UniValue params(UniValue::VOBJ);
|
||||
|
||||
for (const std::string &s: strParams) {
|
||||
size_t pos = s.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
|
||||
}
|
||||
|
||||
std::string name = s.substr(0, pos);
|
||||
std::string value = s.substr(pos+1);
|
||||
|
||||
if (!rpcCvtTable.convert(strMethod, name)) {
|
||||
// insert string value directly
|
||||
params.pushKV(name, value);
|
||||
} else {
|
||||
// parse string as JSON, insert bool/number/object/etc. value
|
||||
params.pushKV(name, ParseNonRFCJSONValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
22
src/rpc/client.h
Normal file
22
src/rpc/client.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2018 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_CLIENT_H
|
||||
#define DIGIBYTE_RPC_CLIENT_H
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
/** Convert positional arguments to command-specific RPC representation */
|
||||
UniValue RPCConvertValues(const std::string& strMethod, const std::vector<std::string>& strParams);
|
||||
|
||||
/** Convert named arguments to command-specific RPC representation */
|
||||
UniValue RPCConvertNamedValues(const std::string& strMethod, const std::vector<std::string>& strParams);
|
||||
|
||||
/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
|
||||
* as well as objects and arrays.
|
||||
*/
|
||||
UniValue ParseNonRFCJSONValue(const std::string& strVal);
|
||||
|
||||
#endif // DIGIBYTE_RPC_CLIENT_H
|
||||
76
src/rpc/external_signer.cpp
Normal file
76
src/rpc/external_signer.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2018-2021 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <chainparamsbase.h>
|
||||
#include <external_signer.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/util.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <rpc/protocol.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
|
||||
static RPCHelpMan enumeratesigners()
|
||||
{
|
||||
return RPCHelpMan{"enumeratesigners",
|
||||
"Returns a list of external signers from -signer.",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR, "signers", /* optional */ false, "",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "masterkeyfingerprint", "Master key fingerprint"},
|
||||
{RPCResult::Type::STR, "name", "Device name"},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("enumeratesigners", "")
|
||||
+ HelpExampleRpc("enumeratesigners", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::string command = gArgs.GetArg("-signer", "");
|
||||
if (command == "") throw JSONRPCError(RPC_MISC_ERROR, "Error: restart vextad with -signer=<cmd>");
|
||||
const std::string chain = gArgs.GetChainName();
|
||||
UniValue signers_res = UniValue::VARR;
|
||||
try {
|
||||
std::vector<ExternalSigner> signers;
|
||||
ExternalSigner::Enumerate(command, signers, chain);
|
||||
for (const ExternalSigner& signer : signers) {
|
||||
UniValue signer_res = UniValue::VOBJ;
|
||||
signer_res.pushKV("fingerprint", signer.m_fingerprint);
|
||||
signer_res.pushKV("name", signer.m_name);
|
||||
signers_res.push_back(signer_res);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, e.what());
|
||||
}
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("signers", signers_res);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void RegisterSignerRPCCommands(CRPCTable &t)
|
||||
{
|
||||
// clang-format off
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category actor (function)
|
||||
// --------------------- ------------------------
|
||||
{ "signer", &enumeratesigners, },
|
||||
};
|
||||
// clang-format on
|
||||
for (const auto& c : commands) {
|
||||
t.appendCommand(c.name, &c);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_EXTERNAL_SIGNER
|
||||
1298
src/rpc/mining.cpp
Normal file
1298
src/rpc/mining.cpp
Normal file
File diff suppressed because it is too large
Load diff
12
src/rpc/mining.h
Normal file
12
src/rpc/mining.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_MINING_H
|
||||
#define DIGIBYTE_RPC_MINING_H
|
||||
|
||||
/** Default max iterations to try in RPC generatetodescriptor, generatetoaddress, and generateblock. */
|
||||
static const uint64_t DEFAULT_MAX_TRIES{1000000};
|
||||
|
||||
#endif // DIGIBYTE_RPC_MINING_H
|
||||
777
src/rpc/misc.cpp
Normal file
777
src/rpc/misc.cpp
Normal file
|
|
@ -0,0 +1,777 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <httpserver.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/echo.h>
|
||||
#include <interfaces/init.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <key_io.h>
|
||||
#include <node/context.h>
|
||||
#include <outputtype.h>
|
||||
#include <rpc/blockchain.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/util.h>
|
||||
#include <scheduler.h>
|
||||
#include <script/descriptor.h>
|
||||
#include <util/check.h>
|
||||
#include <util/message.h> // For MessageSign(), MessageVerify()
|
||||
#include <util/strencodings.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <tuple>
|
||||
#ifdef HAVE_MALLOC_INFO
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
static RPCHelpMan validateaddress()
|
||||
{
|
||||
return RPCHelpMan{"validateaddress",
|
||||
"\nReturn information about the given Vexta address.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Vexta address to validate"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
|
||||
{RPCResult::Type::STR, "address", "The Vexta address validated"},
|
||||
{RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address"},
|
||||
{RPCResult::Type::BOOL, "isscript", "If the key is a script"},
|
||||
{RPCResult::Type::BOOL, "iswitness", "If the address is a witness address"},
|
||||
{RPCResult::Type::NUM, "witness_version", /* optional */ true, "The version number of the witness program"},
|
||||
{RPCResult::Type::STR_HEX, "witness_program", /* optional */ true, "The hex value of the witness program"},
|
||||
{RPCResult::Type::STR, "error", /* optional */ true, "Error message, if any"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
|
||||
HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string error_msg;
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
|
||||
const bool isValid = IsValidDestination(dest);
|
||||
CHECK_NONFATAL(isValid == error_msg.empty());
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("isvalid", isValid);
|
||||
if (isValid) {
|
||||
std::string currentAddress = EncodeDestination(dest);
|
||||
ret.pushKV("address", currentAddress);
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(dest);
|
||||
ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
||||
|
||||
UniValue detail = DescribeAddress(dest);
|
||||
ret.pushKVs(detail);
|
||||
} else {
|
||||
ret.pushKV("error", error_msg);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan createmultisig()
|
||||
{
|
||||
return RPCHelpMan{"createmultisig",
|
||||
"\nCreates a multi-signature address with n signature of m keys required.\n"
|
||||
"It returns a json object with the address and redeemScript.\n",
|
||||
{
|
||||
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
|
||||
{"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
|
||||
{
|
||||
{"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
|
||||
}},
|
||||
{"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The value of the new multisig address."},
|
||||
{RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
|
||||
{RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nCreate a multisig address from 2 public keys\n"
|
||||
+ HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
int required = request.params[0].get_int();
|
||||
|
||||
// Get the public keys
|
||||
const UniValue& keys = request.params[1].get_array();
|
||||
std::vector<CPubKey> pubkeys;
|
||||
for (unsigned int i = 0; i < keys.size(); ++i) {
|
||||
if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
|
||||
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the output type
|
||||
OutputType output_type = OutputType::LEGACY;
|
||||
if (!request.params[2].isNull()) {
|
||||
if (!ParseOutputType(request.params[2].get_str(), output_type)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
|
||||
}
|
||||
if (output_type == OutputType::BECH32M) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
|
||||
}
|
||||
}
|
||||
|
||||
// Construct using pay-to-script-hash:
|
||||
FillableSigningProvider keystore;
|
||||
CScript inner;
|
||||
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
|
||||
|
||||
// Make the descriptor
|
||||
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("address", EncodeDestination(dest));
|
||||
result.pushKV("redeemScript", HexStr(inner));
|
||||
result.pushKV("descriptor", descriptor->ToString());
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getdescriptorinfo()
|
||||
{
|
||||
return RPCHelpMan{"getdescriptorinfo",
|
||||
{"\nAnalyses a descriptor.\n"},
|
||||
{
|
||||
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"},
|
||||
{RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"},
|
||||
{RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"},
|
||||
{RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"},
|
||||
{RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"Analyse a descriptor\n" +
|
||||
HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR});
|
||||
|
||||
FlatSigningProvider provider;
|
||||
std::string error;
|
||||
auto desc = Parse(request.params[0].get_str(), provider, error);
|
||||
if (!desc) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("descriptor", desc->ToString());
|
||||
result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str()));
|
||||
result.pushKV("isrange", desc->IsRange());
|
||||
result.pushKV("issolvable", desc->IsSolvable());
|
||||
result.pushKV("hasprivatekeys", provider.keys.size() > 0);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan deriveaddresses()
|
||||
{
|
||||
return RPCHelpMan{"deriveaddresses",
|
||||
{"\nDerives one or more addresses corresponding to an output descriptor.\n"
|
||||
"Examples of output descriptors are:\n"
|
||||
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
|
||||
" wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n"
|
||||
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
|
||||
" raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
|
||||
"\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
|
||||
"or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n"
|
||||
"For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
|
||||
{
|
||||
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
|
||||
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "the derived addresses"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"First three native segwit receive addresses\n" +
|
||||
HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
|
||||
const std::string desc_str = request.params[0].get_str();
|
||||
|
||||
int64_t range_begin = 0;
|
||||
int64_t range_end = 0;
|
||||
|
||||
if (request.params.size() >= 2 && !request.params[1].isNull()) {
|
||||
std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]);
|
||||
}
|
||||
|
||||
FlatSigningProvider key_provider;
|
||||
std::string error;
|
||||
auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true);
|
||||
if (!desc) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
|
||||
}
|
||||
|
||||
if (!desc->IsRange() && request.params.size() > 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
|
||||
}
|
||||
|
||||
if (desc->IsRange() && request.params.size() == 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor");
|
||||
}
|
||||
|
||||
UniValue addresses(UniValue::VARR);
|
||||
|
||||
for (int i = range_begin; i <= range_end; ++i) {
|
||||
FlatSigningProvider provider;
|
||||
std::vector<CScript> scripts;
|
||||
if (!desc->Expand(i, key_provider, scripts, provider)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys"));
|
||||
}
|
||||
|
||||
for (const CScript &script : scripts) {
|
||||
CTxDestination dest;
|
||||
if (!ExtractDestination(script, dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Descriptor does not have a corresponding address"));
|
||||
}
|
||||
|
||||
addresses.push_back(EncodeDestination(dest));
|
||||
}
|
||||
}
|
||||
|
||||
// This should not be possible, but an assert seems overkill:
|
||||
if (addresses.empty()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result");
|
||||
}
|
||||
|
||||
return addresses;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan verifymessage()
|
||||
{
|
||||
return RPCHelpMan{"verifymessage",
|
||||
"\nVerify a signed message\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Vexta address to use for the signature."},
|
||||
{"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."},
|
||||
{"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::BOOL, "", "If the signature is verified or not."
|
||||
},
|
||||
RPCExamples{
|
||||
"\nUnlock the wallet for 30 seconds\n"
|
||||
+ HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
|
||||
"\nCreate the signature\n"
|
||||
+ HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
|
||||
"\nVerify the signature\n"
|
||||
+ HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
||||
std::string strAddress = request.params[0].get_str();
|
||||
std::string strSign = request.params[1].get_str();
|
||||
std::string strMessage = request.params[2].get_str();
|
||||
|
||||
switch (MessageVerify(strAddress, strSign, strMessage)) {
|
||||
case MessageVerificationResult::ERR_INVALID_ADDRESS:
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
|
||||
case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding");
|
||||
case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
|
||||
case MessageVerificationResult::ERR_NOT_SIGNED:
|
||||
return false;
|
||||
case MessageVerificationResult::OK:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan signmessagewithprivkey()
|
||||
{
|
||||
return RPCHelpMan{"signmessagewithprivkey",
|
||||
"\nSign a message with the private key of an address\n",
|
||||
{
|
||||
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
|
||||
{"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64"
|
||||
},
|
||||
RPCExamples{
|
||||
"\nCreate the signature\n"
|
||||
+ HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") +
|
||||
"\nVerify the signature\n"
|
||||
+ HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string strPrivkey = request.params[0].get_str();
|
||||
std::string strMessage = request.params[1].get_str();
|
||||
|
||||
CKey key = DecodeSecret(strPrivkey);
|
||||
if (!key.IsValid()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
|
||||
}
|
||||
|
||||
std::string signature;
|
||||
|
||||
if (!MessageSign(key, strMessage, signature)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
|
||||
}
|
||||
|
||||
return signature;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan setmocktime()
|
||||
{
|
||||
return RPCHelpMan{"setmocktime",
|
||||
"\nSet the local time to given timestamp (-regtest only)\n",
|
||||
{
|
||||
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
|
||||
"Pass 0 to go back to using the system time."},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
if (!Params().IsMockableChain()) {
|
||||
throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
|
||||
}
|
||||
|
||||
// For now, don't change mocktime if we're in the middle of validation, as
|
||||
// this could have an effect on mempool time-based eviction, as well as
|
||||
// IsCurrentForFeeEstimation() and IsInitialBlockDownload().
|
||||
// TODO: figure out the right way to synchronize around mocktime, and
|
||||
// ensure all call sites of GetTime() are accessing this safely.
|
||||
LOCK(cs_main);
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VNUM});
|
||||
const int64_t time{request.params[0].get_int64()};
|
||||
if (time < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime can not be negative: %s.", time));
|
||||
}
|
||||
SetMockTime(time);
|
||||
auto node_context = util::AnyPtr<NodeContext>(request.context);
|
||||
if (node_context) {
|
||||
for (const auto& chain_client : node_context->chain_clients) {
|
||||
chain_client->setMockTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan mockscheduler()
|
||||
{
|
||||
return RPCHelpMan{"mockscheduler",
|
||||
"\nBump the scheduler into the future (-regtest only)\n",
|
||||
{
|
||||
{"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
if (!Params().IsMockableChain()) {
|
||||
throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
|
||||
}
|
||||
|
||||
// check params are valid values
|
||||
RPCTypeCheck(request.params, {UniValue::VNUM});
|
||||
int64_t delta_seconds = request.params[0].get_int64();
|
||||
if (delta_seconds <= 0 || delta_seconds > 3600) {
|
||||
throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
|
||||
}
|
||||
|
||||
auto node_context = util::AnyPtr<NodeContext>(request.context);
|
||||
// protect against null pointer dereference
|
||||
CHECK_NONFATAL(node_context);
|
||||
CHECK_NONFATAL(node_context->scheduler);
|
||||
node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static UniValue RPCLockedMemoryInfo()
|
||||
{
|
||||
LockedPool::Stats stats = LockedPoolManager::Instance().stats();
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("used", uint64_t(stats.used));
|
||||
obj.pushKV("free", uint64_t(stats.free));
|
||||
obj.pushKV("total", uint64_t(stats.total));
|
||||
obj.pushKV("locked", uint64_t(stats.locked));
|
||||
obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
|
||||
obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
|
||||
return obj;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MALLOC_INFO
|
||||
static std::string RPCMallocInfo()
|
||||
{
|
||||
char *ptr = nullptr;
|
||||
size_t size = 0;
|
||||
FILE *f = open_memstream(&ptr, &size);
|
||||
if (f) {
|
||||
malloc_info(0, f);
|
||||
fclose(f);
|
||||
if (ptr) {
|
||||
std::string rv(ptr, size);
|
||||
free(ptr);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
static RPCHelpMan getmemoryinfo()
|
||||
{
|
||||
/* Please, avoid using the word "pool" here in the RPC interface or help,
|
||||
* as users will undoubtedly confuse it with the other "memory pool"
|
||||
*/
|
||||
return RPCHelpMan{"getmemoryinfo",
|
||||
"Returns an object containing information about memory usage.\n",
|
||||
{
|
||||
{"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
|
||||
" - \"stats\" returns general statistics about memory usage in the daemon.\n"
|
||||
" - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."},
|
||||
},
|
||||
{
|
||||
RPCResult{"mode \"stats\"",
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
|
||||
{
|
||||
{RPCResult::Type::NUM, "used", "Number of bytes used"},
|
||||
{RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
|
||||
{RPCResult::Type::NUM, "total", "Total number of bytes managed"},
|
||||
{RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
|
||||
{RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
|
||||
{RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCResult{"mode \"mallocinfo\"",
|
||||
RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
|
||||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getmemoryinfo", "")
|
||||
+ HelpExampleRpc("getmemoryinfo", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
|
||||
if (mode == "stats") {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("locked", RPCLockedMemoryInfo());
|
||||
return obj;
|
||||
} else if (mode == "mallocinfo") {
|
||||
#ifdef HAVE_MALLOC_INFO
|
||||
return RPCMallocInfo();
|
||||
#else
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo is only available when compiled with glibc 2.10+");
|
||||
#endif
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
|
||||
cats = cats.get_array();
|
||||
for (unsigned int i = 0; i < cats.size(); ++i) {
|
||||
std::string cat = cats[i].get_str();
|
||||
|
||||
bool success;
|
||||
if (enable) {
|
||||
success = LogInstance().EnableCategory(cat);
|
||||
} else {
|
||||
success = LogInstance().DisableCategory(cat);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RPCHelpMan logging()
|
||||
{
|
||||
return RPCHelpMan{"logging",
|
||||
"Gets and sets the logging configuration.\n"
|
||||
"When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
|
||||
"When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
|
||||
"The arguments are evaluated in order \"include\", \"exclude\".\n"
|
||||
"If an item is both included and excluded, it will thus end up being excluded.\n"
|
||||
"The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
|
||||
"In addition, the following are available as category names with special meanings:\n"
|
||||
" - \"all\", \"1\" : represent all logging categories.\n"
|
||||
" - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
|
||||
,
|
||||
{
|
||||
{"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging",
|
||||
{
|
||||
{"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
|
||||
}},
|
||||
{"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
|
||||
{
|
||||
{"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
|
||||
}},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
|
||||
+ HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
uint32_t original_log_categories = LogInstance().GetCategoryMask();
|
||||
if (request.params[0].isArray()) {
|
||||
EnableOrDisableLogCategories(request.params[0], true);
|
||||
}
|
||||
if (request.params[1].isArray()) {
|
||||
EnableOrDisableLogCategories(request.params[1], false);
|
||||
}
|
||||
uint32_t updated_log_categories = LogInstance().GetCategoryMask();
|
||||
uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
|
||||
|
||||
// Update libevent logging if BCLog::LIBEVENT has changed.
|
||||
// If the library version doesn't allow it, UpdateHTTPServerLogging() returns false,
|
||||
// in which case we should clear the BCLog::LIBEVENT flag.
|
||||
// Throw an error if the user has explicitly asked to change only the libevent
|
||||
// flag and it failed.
|
||||
if (changed_log_categories & BCLog::LIBEVENT) {
|
||||
if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
|
||||
LogInstance().DisableCategory(BCLog::LIBEVENT);
|
||||
if (changed_log_categories == BCLog::LIBEVENT) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
for (const auto& logCatActive : LogInstance().LogCategoriesList()) {
|
||||
result.pushKV(logCatActive.category, logCatActive.active);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan echo(const std::string& name)
|
||||
{
|
||||
return RPCHelpMan{name,
|
||||
"\nSimply echo back the input arguments. This command is for testing.\n"
|
||||
"\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
|
||||
"\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
|
||||
"vexta-cli and the GUI. There is no server-side difference.",
|
||||
{
|
||||
{"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
{"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
|
||||
},
|
||||
RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
if (request.params[9].isStr()) {
|
||||
CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
|
||||
}
|
||||
|
||||
return request.params;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan echo() { return echo("echo"); }
|
||||
static RPCHelpMan echojson() { return echo("echojson"); }
|
||||
|
||||
static RPCHelpMan echoipc()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
"echoipc",
|
||||
"\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n"
|
||||
"This command is for testing.\n",
|
||||
{{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
|
||||
RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
|
||||
RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
|
||||
HelpExampleRpc("echo", "\"Hello world\"")},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
|
||||
std::unique_ptr<interfaces::Echo> echo;
|
||||
if (interfaces::Ipc* ipc = Assert(EnsureAnyNodeContext(request.context).init)->ipc()) {
|
||||
// Spawn a new vexta-node process and call makeEcho to get a
|
||||
// client pointer to a interfaces::Echo instance running in
|
||||
// that process. This is just for testing. A slightly more
|
||||
// realistic test spawning a different executable instead of
|
||||
// the same executable would add a new digibyte-echo executable,
|
||||
// and spawn digibyte-echo below instead of vexta-node. But
|
||||
// using vexta-node avoids the need to build and install a
|
||||
// new executable just for this one test.
|
||||
auto init = ipc->spawnProcess("vexta-node");
|
||||
echo = init->makeEcho();
|
||||
ipc->addCleanup(*echo, [init = init.release()] { delete init; });
|
||||
} else {
|
||||
// IPC support is not available because this is a vextad
|
||||
// process not a vextad-node process, so just create a local
|
||||
// interfaces::Echo object and return it so the `echoipc` RPC
|
||||
// method will work, and the python test calling `echoipc`
|
||||
// can expect the same result.
|
||||
echo = interfaces::MakeEcho();
|
||||
}
|
||||
return echo->echo(request.params[0].get_str());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name)
|
||||
{
|
||||
UniValue ret_summary(UniValue::VOBJ);
|
||||
if (!index_name.empty() && index_name != summary.name) return ret_summary;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("synced", summary.synced);
|
||||
entry.pushKV("best_block_height", summary.best_block_height);
|
||||
ret_summary.pushKV(summary.name, entry);
|
||||
return ret_summary;
|
||||
}
|
||||
|
||||
static RPCHelpMan getindexinfo()
|
||||
{
|
||||
return RPCHelpMan{"getindexinfo",
|
||||
"\nReturns the status of one or all available indices currently running in the node.\n",
|
||||
{
|
||||
{"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "", {
|
||||
{
|
||||
RPCResult::Type::OBJ, "name", "The name of the index",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"},
|
||||
{RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getindexinfo", "")
|
||||
+ HelpExampleRpc("getindexinfo", "")
|
||||
+ HelpExampleCli("getindexinfo", "txindex")
|
||||
+ HelpExampleRpc("getindexinfo", "txindex")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
UniValue result(UniValue::VOBJ);
|
||||
const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str();
|
||||
|
||||
if (g_txindex) {
|
||||
result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
if (g_coin_stats_index) {
|
||||
result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
|
||||
result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void RegisterMiscRPCCommands(CRPCTable &t)
|
||||
{
|
||||
// clang-format off
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category actor (function)
|
||||
// --------------------- ------------------------
|
||||
{ "control", &getmemoryinfo, },
|
||||
{ "control", &logging, },
|
||||
{ "util", &validateaddress, },
|
||||
{ "util", &createmultisig, },
|
||||
{ "util", &deriveaddresses, },
|
||||
{ "util", &getdescriptorinfo, },
|
||||
{ "util", &verifymessage, },
|
||||
{ "util", &signmessagewithprivkey, },
|
||||
{ "util", &getindexinfo, },
|
||||
|
||||
/* Not shown in help */
|
||||
{ "hidden", &setmocktime, },
|
||||
{ "hidden", &mockscheduler, },
|
||||
{ "hidden", &echo, },
|
||||
{ "hidden", &echojson, },
|
||||
{ "hidden", &echoipc, },
|
||||
};
|
||||
// clang-format on
|
||||
for (const auto& c : commands) {
|
||||
t.appendCommand(c.name, &c);
|
||||
}
|
||||
}
|
||||
987
src/rpc/net.cpp
Normal file
987
src/rpc/net.cpp
Normal file
|
|
@ -0,0 +1,987 @@
|
|||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <rpc/server.h>
|
||||
|
||||
#include <banman.h>
|
||||
#include <chainparams.h>
|
||||
#include <clientversion.h>
|
||||
#include <core_io.h>
|
||||
#include <net.h>
|
||||
#include <net_permissions.h>
|
||||
#include <net_processing.h>
|
||||
#include <net_types.h> // For banmap_t
|
||||
#include <netbase.h>
|
||||
#include <node/context.h>
|
||||
#include <policy/settings.h>
|
||||
#include <rpc/blockchain.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <rpc/util.h>
|
||||
#include <sync.h>
|
||||
#include <timedata.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <util/system.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <version.h>
|
||||
#include <warnings.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
const std::vector<std::string> CONNECTION_TYPE_DOC{
|
||||
"outbound-full-relay (default automatic connections)",
|
||||
"block-relay-only (does not relay transactions or addresses)",
|
||||
"inbound (initiated by the peer)",
|
||||
"manual (added via addnode RPC or -addnode/-connect configuration options)",
|
||||
"addr-fetch (short-lived automatic connection for soliciting addresses)",
|
||||
"feeler (short-lived automatic connection for testing addresses)"
|
||||
};
|
||||
|
||||
CConnman& EnsureConnman(const NodeContext& node)
|
||||
{
|
||||
if (!node.connman) {
|
||||
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
|
||||
}
|
||||
return *node.connman;
|
||||
}
|
||||
|
||||
PeerManager& EnsurePeerman(const NodeContext& node)
|
||||
{
|
||||
if (!node.peerman) {
|
||||
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
|
||||
}
|
||||
return *node.peerman;
|
||||
}
|
||||
|
||||
static RPCHelpMan getconnectioncount()
|
||||
{
|
||||
return RPCHelpMan{"getconnectioncount",
|
||||
"\nReturns the number of connections to other nodes.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::NUM, "", "The connection count"
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getconnectioncount", "")
|
||||
+ HelpExampleRpc("getconnectioncount", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CConnman& connman = EnsureConnman(node);
|
||||
|
||||
return (int)connman.GetNodeCount(ConnectionDirection::Both);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan ping()
|
||||
{
|
||||
return RPCHelpMan{"ping",
|
||||
"\nRequests that a ping be sent to all other nodes, to measure ping time.\n"
|
||||
"Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n"
|
||||
"Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping.\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("ping", "")
|
||||
+ HelpExampleRpc("ping", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
PeerManager& peerman = EnsurePeerman(node);
|
||||
|
||||
// Request that each node send a ping during next message processing pass
|
||||
peerman.SendPings();
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getpeerinfo()
|
||||
{
|
||||
return RPCHelpMan{"getpeerinfo",
|
||||
"\nReturns data about each connected network node as a json array of objects.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{
|
||||
{RPCResult::Type::NUM, "id", "Peer index"},
|
||||
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
|
||||
{RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"},
|
||||
{RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"},
|
||||
{RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
|
||||
{RPCResult::Type::NUM, "mapped_as", "The AS in the BGP route to the peer used for diversifying\n"
|
||||
"peer selection (only available if the asmap config flag is set)"},
|
||||
{RPCResult::Type::STR_HEX, "services", "The services offered"},
|
||||
{RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form",
|
||||
{
|
||||
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
|
||||
}},
|
||||
{RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"},
|
||||
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
|
||||
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
|
||||
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
|
||||
{RPCResult::Type::NUM_TIME, "last_block", "The " + UNIX_EPOCH_TIME + " of the last block received from this peer"},
|
||||
{RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
|
||||
{RPCResult::Type::NUM, "bytesrecv", "The total bytes received"},
|
||||
{RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"},
|
||||
{RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"},
|
||||
{RPCResult::Type::NUM, "pingtime", "ping time (if available)"},
|
||||
{RPCResult::Type::NUM, "minping", "minimum observed ping time (if any at all)"},
|
||||
{RPCResult::Type::NUM, "pingwait", "ping wait (if non-zero)"},
|
||||
{RPCResult::Type::NUM, "version", "The peer version, such as 70001"},
|
||||
{RPCResult::Type::STR, "subver", "The string version"},
|
||||
{RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"},
|
||||
{RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"},
|
||||
{RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"},
|
||||
{RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"},
|
||||
{RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"},
|
||||
{RPCResult::Type::NUM, "synced_blocks", "The last block we have in common with this peer"},
|
||||
{RPCResult::Type::ARR, "inflight", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"},
|
||||
}},
|
||||
{RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer",
|
||||
{
|
||||
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"},
|
||||
{RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
|
||||
"When a message type is not listed in this json object, the bytes sent are 0.\n"
|
||||
"Only known message types can appear as keys in the object."}
|
||||
}},
|
||||
{RPCResult::Type::OBJ_DYN, "bytesrecv_per_msg", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n"
|
||||
"When a message type is not listed in this json object, the bytes received are 0.\n"
|
||||
"Only known message types can appear as keys in the object and all bytes received\n"
|
||||
"of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."}
|
||||
}},
|
||||
{RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n"
|
||||
"Please note this output is unlikely to be stable in upcoming releases as we iterate to\n"
|
||||
"best capture connection behaviors."},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getpeerinfo", "")
|
||||
+ HelpExampleRpc("getpeerinfo", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CConnman& connman = EnsureConnman(node);
|
||||
const PeerManager& peerman = EnsurePeerman(node);
|
||||
|
||||
std::vector<CNodeStats> vstats;
|
||||
connman.GetNodeStats(vstats);
|
||||
|
||||
UniValue ret(UniValue::VARR);
|
||||
|
||||
for (const CNodeStats& stats : vstats) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CNodeStateStats statestats;
|
||||
bool fStateStats = peerman.GetNodeStateStats(stats.nodeid, statestats);
|
||||
obj.pushKV("id", stats.nodeid);
|
||||
obj.pushKV("addr", stats.addrName);
|
||||
if (stats.addrBind.IsValid()) {
|
||||
obj.pushKV("addrbind", stats.addrBind.ToString());
|
||||
}
|
||||
if (!(stats.addrLocal.empty())) {
|
||||
obj.pushKV("addrlocal", stats.addrLocal);
|
||||
}
|
||||
obj.pushKV("network", GetNetworkName(stats.m_network));
|
||||
if (stats.m_mapped_as != 0) {
|
||||
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
|
||||
}
|
||||
obj.pushKV("services", strprintf("%016x", stats.nServices));
|
||||
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
|
||||
obj.pushKV("relaytxes", stats.fRelayTxes);
|
||||
obj.pushKV("lastsend", stats.nLastSend);
|
||||
obj.pushKV("lastrecv", stats.nLastRecv);
|
||||
obj.pushKV("last_transaction", stats.nLastTXTime);
|
||||
obj.pushKV("last_block", stats.nLastBlockTime);
|
||||
obj.pushKV("bytessent", stats.nSendBytes);
|
||||
obj.pushKV("bytesrecv", stats.nRecvBytes);
|
||||
obj.pushKV("conntime", stats.nTimeConnected);
|
||||
obj.pushKV("timeoffset", stats.nTimeOffset);
|
||||
if (stats.m_last_ping_time > 0us) {
|
||||
obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time));
|
||||
}
|
||||
if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
|
||||
obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time));
|
||||
}
|
||||
if (fStateStats && statestats.m_ping_wait > 0s) {
|
||||
obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait));
|
||||
}
|
||||
obj.pushKV("version", stats.nVersion);
|
||||
// Use the sanitized form of subver here, to avoid tricksy remote peers from
|
||||
// corrupting or modifying the JSON output by putting special characters in
|
||||
// their ver message.
|
||||
obj.pushKV("subver", stats.cleanSubVer);
|
||||
obj.pushKV("inbound", stats.fInbound);
|
||||
obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to);
|
||||
obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
|
||||
if (fStateStats) {
|
||||
obj.pushKV("startingheight", statestats.m_starting_height);
|
||||
obj.pushKV("synced_headers", statestats.nSyncHeight);
|
||||
obj.pushKV("synced_blocks", statestats.nCommonHeight);
|
||||
UniValue heights(UniValue::VARR);
|
||||
for (const int height : statestats.vHeightInFlight) {
|
||||
heights.push_back(height);
|
||||
}
|
||||
obj.pushKV("inflight", heights);
|
||||
obj.pushKV("addr_processed", statestats.m_addr_processed);
|
||||
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
|
||||
}
|
||||
UniValue permissions(UniValue::VARR);
|
||||
for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) {
|
||||
permissions.push_back(permission);
|
||||
}
|
||||
obj.pushKV("permissions", permissions);
|
||||
obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
|
||||
|
||||
UniValue sendPerMsgCmd(UniValue::VOBJ);
|
||||
for (const auto& i : stats.mapSendBytesPerMsgCmd) {
|
||||
if (i.second > 0)
|
||||
sendPerMsgCmd.pushKV(i.first, i.second);
|
||||
}
|
||||
obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
|
||||
|
||||
UniValue recvPerMsgCmd(UniValue::VOBJ);
|
||||
for (const auto& i : stats.mapRecvBytesPerMsgCmd) {
|
||||
if (i.second > 0)
|
||||
recvPerMsgCmd.pushKV(i.first, i.second);
|
||||
}
|
||||
obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
|
||||
obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type));
|
||||
|
||||
ret.push_back(obj);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan addnode()
|
||||
{
|
||||
return RPCHelpMan{"addnode",
|
||||
"\nAttempts to add or remove a node from the addnode list.\n"
|
||||
"Or try a connection to a node once.\n"
|
||||
"Nodes added using addnode (or -connect) are protected from DoS disconnection and are not required to be\n"
|
||||
"full nodes/support SegWit as other outbound peers are (though such peers will not be synced from).\n" +
|
||||
strprintf("Addnode connections are limited to %u at a time", MAX_ADDNODE_CONNECTIONS) +
|
||||
" and are counted separately from the -maxconnections limit.\n",
|
||||
{
|
||||
{"node", RPCArg::Type::STR, RPCArg::Optional::NO, "The node (see getpeerinfo for nodes)"},
|
||||
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("addnode", "\"192.168.0.6:12024\" \"onetry\"")
|
||||
+ HelpExampleRpc("addnode", "\"192.168.0.6:12024\", \"onetry\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string strCommand;
|
||||
if (!request.params[1].isNull())
|
||||
strCommand = request.params[1].get_str();
|
||||
if (strCommand != "onetry" && strCommand != "add" && strCommand != "remove") {
|
||||
throw std::runtime_error(
|
||||
self.ToString());
|
||||
}
|
||||
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
|
||||
std::string strNode = request.params[0].get_str();
|
||||
|
||||
if (strCommand == "onetry")
|
||||
{
|
||||
CAddress addr;
|
||||
connman.OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL);
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
if (strCommand == "add")
|
||||
{
|
||||
if (!connman.AddNode(strNode)) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added");
|
||||
}
|
||||
}
|
||||
else if(strCommand == "remove")
|
||||
{
|
||||
if (!connman.RemoveAddedNode(strNode)) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node could not be removed. It has not been added previously.");
|
||||
}
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan addconnection()
|
||||
{
|
||||
return RPCHelpMan{"addconnection",
|
||||
"\nOpen an outbound connection to a specified node. This RPC is for testing only.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address and port to attempt connecting to."},
|
||||
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\" or \"addr-fetch\")."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{ RPCResult::Type::STR, "address", "Address of newly added connection." },
|
||||
{ RPCResult::Type::STR, "connection_type", "Type of connection opened." },
|
||||
}},
|
||||
RPCExamples{
|
||||
HelpExampleCli("addconnection", "\"192.168.0.6:12024\" \"outbound-full-relay\"")
|
||||
+ HelpExampleRpc("addconnection", "\"192.168.0.6:12024\" \"outbound-full-relay\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
if (Params().NetworkIDString() != CBaseChainParams::REGTEST) {
|
||||
throw std::runtime_error("addconnection is for regression testing (-regtest mode) only.");
|
||||
}
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR});
|
||||
const std::string address = request.params[0].get_str();
|
||||
const std::string conn_type_in{TrimString(request.params[1].get_str())};
|
||||
ConnectionType conn_type{};
|
||||
if (conn_type_in == "outbound-full-relay") {
|
||||
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
|
||||
} else if (conn_type_in == "block-relay-only") {
|
||||
conn_type = ConnectionType::BLOCK_RELAY;
|
||||
} else if (conn_type_in == "addr-fetch") {
|
||||
conn_type = ConnectionType::ADDR_FETCH;
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
|
||||
}
|
||||
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
|
||||
const bool success = connman.AddConnection(address, conn_type);
|
||||
if (!success) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_CAPACITY_REACHED, "Error: Already at capacity for specified connection type.");
|
||||
}
|
||||
|
||||
UniValue info(UniValue::VOBJ);
|
||||
info.pushKV("address", address);
|
||||
info.pushKV("connection_type", conn_type_in);
|
||||
|
||||
return info;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan disconnectnode()
|
||||
{
|
||||
return RPCHelpMan{"disconnectnode",
|
||||
"\nImmediately disconnects from the specified peer node.\n"
|
||||
"\nStrictly one out of 'address' and 'nodeid' can be provided to identify the node.\n"
|
||||
"\nTo disconnect by nodeid, either set 'address' to the empty string, or call using the named 'nodeid' argument only.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::DefaultHint{"fallback to nodeid"}, "The IP address/port of the node"},
|
||||
{"nodeid", RPCArg::Type::NUM, RPCArg::DefaultHint{"fallback to address"}, "The node ID (see getpeerinfo for node IDs)"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("disconnectnode", "\"192.168.0.6:12024\"")
|
||||
+ HelpExampleCli("disconnectnode", "\"\" 1")
|
||||
+ HelpExampleRpc("disconnectnode", "\"192.168.0.6:12024\"")
|
||||
+ HelpExampleRpc("disconnectnode", "\"\", 1")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
|
||||
bool success;
|
||||
const UniValue &address_arg = request.params[0];
|
||||
const UniValue &id_arg = request.params[1];
|
||||
|
||||
if (!address_arg.isNull() && id_arg.isNull()) {
|
||||
/* handle disconnect-by-address */
|
||||
success = connman.DisconnectNode(address_arg.get_str());
|
||||
} else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) {
|
||||
/* handle disconnect-by-id */
|
||||
NodeId nodeid = (NodeId) id_arg.get_int64();
|
||||
success = connman.DisconnectNode(nodeid);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided.");
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes");
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getaddednodeinfo()
|
||||
{
|
||||
return RPCHelpMan{"getaddednodeinfo",
|
||||
"\nReturns information about the given added node, or all added nodes\n"
|
||||
"(note that onetry addnodes are not listed here)\n",
|
||||
{
|
||||
{"node", RPCArg::Type::STR, RPCArg::DefaultHint{"all nodes"}, "If provided, return information about this specific node, otherwise all nodes are returned."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "addednode", "The node IP address or name (as provided to addnode)"},
|
||||
{RPCResult::Type::BOOL, "connected", "If connected"},
|
||||
{RPCResult::Type::ARR, "addresses", "Only when connected = true",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The Vexta server IP and port we're connected to"},
|
||||
{RPCResult::Type::STR, "connected", "connection, inbound or outbound"},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"")
|
||||
+ HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CConnman& connman = EnsureConnman(node);
|
||||
|
||||
std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo();
|
||||
|
||||
if (!request.params[0].isNull()) {
|
||||
bool found = false;
|
||||
for (const AddedNodeInfo& info : vInfo) {
|
||||
if (info.strAddedNode == request.params[0].get_str()) {
|
||||
vInfo.assign(1, info);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node has not been added.");
|
||||
}
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VARR);
|
||||
|
||||
for (const AddedNodeInfo& info : vInfo) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("addednode", info.strAddedNode);
|
||||
obj.pushKV("connected", info.fConnected);
|
||||
UniValue addresses(UniValue::VARR);
|
||||
if (info.fConnected) {
|
||||
UniValue address(UniValue::VOBJ);
|
||||
address.pushKV("address", info.resolvedAddress.ToString());
|
||||
address.pushKV("connected", info.fInbound ? "inbound" : "outbound");
|
||||
addresses.push_back(address);
|
||||
}
|
||||
obj.pushKV("addresses", addresses);
|
||||
ret.push_back(obj);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getnettotals()
|
||||
{
|
||||
return RPCHelpMan{"getnettotals",
|
||||
"\nReturns information about network traffic, including bytes in, bytes out,\n"
|
||||
"and current time.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "totalbytesrecv", "Total bytes received"},
|
||||
{RPCResult::Type::NUM, "totalbytessent", "Total bytes sent"},
|
||||
{RPCResult::Type::NUM_TIME, "timemillis", "Current " + UNIX_EPOCH_TIME + " in milliseconds"},
|
||||
{RPCResult::Type::OBJ, "uploadtarget", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "timeframe", "Length of the measuring timeframe in seconds"},
|
||||
{RPCResult::Type::NUM, "target", "Target in bytes"},
|
||||
{RPCResult::Type::BOOL, "target_reached", "True if target is reached"},
|
||||
{RPCResult::Type::BOOL, "serve_historical_blocks", "True if serving historical blocks"},
|
||||
{RPCResult::Type::NUM, "bytes_left_in_cycle", "Bytes left in current time cycle"},
|
||||
{RPCResult::Type::NUM, "time_left_in_cycle", "Seconds left in current time cycle"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getnettotals", "")
|
||||
+ HelpExampleRpc("getnettotals", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CConnman& connman = EnsureConnman(node);
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("totalbytesrecv", connman.GetTotalBytesRecv());
|
||||
obj.pushKV("totalbytessent", connman.GetTotalBytesSent());
|
||||
obj.pushKV("timemillis", GetTimeMillis());
|
||||
|
||||
UniValue outboundLimit(UniValue::VOBJ);
|
||||
outboundLimit.pushKV("timeframe", count_seconds(connman.GetMaxOutboundTimeframe()));
|
||||
outboundLimit.pushKV("target", connman.GetMaxOutboundTarget());
|
||||
outboundLimit.pushKV("target_reached", connman.OutboundTargetReached(false));
|
||||
outboundLimit.pushKV("serve_historical_blocks", !connman.OutboundTargetReached(true));
|
||||
outboundLimit.pushKV("bytes_left_in_cycle", connman.GetOutboundTargetBytesLeft());
|
||||
outboundLimit.pushKV("time_left_in_cycle", count_seconds(connman.GetMaxOutboundTimeLeftInCycle()));
|
||||
obj.pushKV("uploadtarget", outboundLimit);
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static UniValue GetNetworksInfo()
|
||||
{
|
||||
UniValue networks(UniValue::VARR);
|
||||
for (int n = 0; n < NET_MAX; ++n) {
|
||||
enum Network network = static_cast<enum Network>(n);
|
||||
if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue;
|
||||
proxyType proxy;
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
GetProxy(network, proxy);
|
||||
obj.pushKV("name", GetNetworkName(network));
|
||||
obj.pushKV("limited", !IsReachable(network));
|
||||
obj.pushKV("reachable", IsReachable(network));
|
||||
obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort() : std::string());
|
||||
obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
|
||||
networks.push_back(obj);
|
||||
}
|
||||
return networks;
|
||||
}
|
||||
|
||||
static RPCHelpMan getnetworkinfo()
|
||||
{
|
||||
return RPCHelpMan{"getnetworkinfo",
|
||||
"Returns an object containing various state info regarding P2P networking.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "version", "the server version"},
|
||||
{RPCResult::Type::STR, "subversion", "the server subversion string"},
|
||||
{RPCResult::Type::NUM, "protocolversion", "the protocol version"},
|
||||
{RPCResult::Type::STR_HEX, "localservices", "the services we offer to the network"},
|
||||
{RPCResult::Type::ARR, "localservicesnames", "the services we offer to the network, in human-readable form",
|
||||
{
|
||||
{RPCResult::Type::STR, "SERVICE_NAME", "the service name"},
|
||||
}},
|
||||
{RPCResult::Type::BOOL, "localrelay", "true if transaction relay is requested from peers"},
|
||||
{RPCResult::Type::NUM, "timeoffset", "the time offset"},
|
||||
{RPCResult::Type::NUM, "connections", "the total number of connections"},
|
||||
{RPCResult::Type::NUM, "connections_in", "the number of inbound connections"},
|
||||
{RPCResult::Type::NUM, "connections_out", "the number of outbound connections"},
|
||||
{RPCResult::Type::BOOL, "networkactive", "whether p2p networking is enabled"},
|
||||
{RPCResult::Type::ARR, "networks", "information per network",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "name", "network (" + Join(GetNetworkNames(), ", ") + ")"},
|
||||
{RPCResult::Type::BOOL, "limited", "is the network limited using -onlynet?"},
|
||||
{RPCResult::Type::BOOL, "reachable", "is the network reachable?"},
|
||||
{RPCResult::Type::STR, "proxy", "(\"host:port\") the proxy that is used for this network, or empty if none"},
|
||||
{RPCResult::Type::BOOL, "proxy_randomize_credentials", "Whether randomized credentials are used"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "relayfee", "minimum relay fee rate for transactions in " + CURRENCY_UNIT + "/kvB"},
|
||||
{RPCResult::Type::NUM, "incrementalfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"},
|
||||
{RPCResult::Type::ARR, "localaddresses", "list of local addresses",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "network address"},
|
||||
{RPCResult::Type::NUM, "port", "network port"},
|
||||
{RPCResult::Type::NUM, "score", "relative score"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getnetworkinfo", "")
|
||||
+ HelpExampleRpc("getnetworkinfo", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
LOCK(cs_main);
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("version", CLIENT_VERSION);
|
||||
obj.pushKV("subversion", strSubVersion);
|
||||
obj.pushKV("protocolversion",PROTOCOL_VERSION);
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
if (node.connman) {
|
||||
ServiceFlags services = node.connman->GetLocalServices();
|
||||
obj.pushKV("localservices", strprintf("%016x", services));
|
||||
obj.pushKV("localservicesnames", GetServicesNames(services));
|
||||
}
|
||||
if (node.peerman) {
|
||||
obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs());
|
||||
}
|
||||
obj.pushKV("timeoffset", GetTimeOffset());
|
||||
if (node.connman) {
|
||||
obj.pushKV("networkactive", node.connman->GetNetworkActive());
|
||||
obj.pushKV("connections", (int)node.connman->GetNodeCount(ConnectionDirection::Both));
|
||||
obj.pushKV("connections_in", (int)node.connman->GetNodeCount(ConnectionDirection::In));
|
||||
obj.pushKV("connections_out", (int)node.connman->GetNodeCount(ConnectionDirection::Out));
|
||||
}
|
||||
obj.pushKV("networks", GetNetworksInfo());
|
||||
obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
|
||||
obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
|
||||
UniValue localAddresses(UniValue::VARR);
|
||||
{
|
||||
LOCK(cs_mapLocalHost);
|
||||
for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
|
||||
{
|
||||
UniValue rec(UniValue::VOBJ);
|
||||
rec.pushKV("address", item.first.ToString());
|
||||
rec.pushKV("port", item.second.nPort);
|
||||
rec.pushKV("score", item.second.nScore);
|
||||
localAddresses.push_back(rec);
|
||||
}
|
||||
}
|
||||
obj.pushKV("localaddresses", localAddresses);
|
||||
obj.pushKV("warnings", GetWarnings(false).original);
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan setban()
|
||||
{
|
||||
return RPCHelpMan{"setban",
|
||||
"\nAttempts to add or remove an IP/Subnet from the banned list.\n",
|
||||
{
|
||||
{"subnet", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)"},
|
||||
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'add' to add an IP/Subnet to the list, 'remove' to remove an IP/Subnet from the list"},
|
||||
{"bantime", RPCArg::Type::NUM, RPCArg::Default{0}, "time in seconds how long (or until when if [absolute] is set) the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)"},
|
||||
{"absolute", RPCArg::Type::BOOL, RPCArg::Default{false}, "If set, the bantime must be an absolute timestamp expressed in " + UNIX_EPOCH_TIME},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400")
|
||||
+ HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"")
|
||||
+ HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")
|
||||
},
|
||||
[&](const RPCHelpMan& help, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string strCommand;
|
||||
if (!request.params[1].isNull())
|
||||
strCommand = request.params[1].get_str();
|
||||
if (strCommand != "add" && strCommand != "remove") {
|
||||
throw std::runtime_error(help.ToString());
|
||||
}
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
if (!node.banman) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded");
|
||||
}
|
||||
|
||||
CSubNet subNet;
|
||||
CNetAddr netAddr;
|
||||
bool isSubnet = false;
|
||||
|
||||
if (request.params[0].get_str().find('/') != std::string::npos)
|
||||
isSubnet = true;
|
||||
|
||||
if (!isSubnet) {
|
||||
CNetAddr resolved;
|
||||
LookupHost(request.params[0].get_str(), resolved, false);
|
||||
netAddr = resolved;
|
||||
}
|
||||
else
|
||||
LookupSubNet(request.params[0].get_str(), subNet);
|
||||
|
||||
if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) )
|
||||
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Invalid IP/Subnet");
|
||||
|
||||
if (strCommand == "add")
|
||||
{
|
||||
if (isSubnet ? node.banman->IsBanned(subNet) : node.banman->IsBanned(netAddr)) {
|
||||
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned");
|
||||
}
|
||||
|
||||
int64_t banTime = 0; //use standard bantime if not specified
|
||||
if (!request.params[2].isNull())
|
||||
banTime = request.params[2].get_int64();
|
||||
|
||||
bool absolute = false;
|
||||
if (request.params[3].isTrue())
|
||||
absolute = true;
|
||||
|
||||
if (isSubnet) {
|
||||
node.banman->Ban(subNet, banTime, absolute);
|
||||
if (node.connman) {
|
||||
node.connman->DisconnectNode(subNet);
|
||||
}
|
||||
} else {
|
||||
node.banman->Ban(netAddr, banTime, absolute);
|
||||
if (node.connman) {
|
||||
node.connman->DisconnectNode(netAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(strCommand == "remove")
|
||||
{
|
||||
if (!( isSubnet ? node.banman->Unban(subNet) : node.banman->Unban(netAddr) )) {
|
||||
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned.");
|
||||
}
|
||||
}
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan listbanned()
|
||||
{
|
||||
return RPCHelpMan{"listbanned",
|
||||
"\nList all manually banned IPs/Subnets.\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The IP/Subnet of the banned node"},
|
||||
{RPCResult::Type::NUM_TIME, "ban_created", "The " + UNIX_EPOCH_TIME + " the ban was created"},
|
||||
{RPCResult::Type::NUM_TIME, "banned_until", "The " + UNIX_EPOCH_TIME + " the ban expires"},
|
||||
{RPCResult::Type::NUM_TIME, "ban_duration", "The ban duration, in seconds"},
|
||||
{RPCResult::Type::NUM_TIME, "time_remaining", "The time remaining until the ban expires, in seconds"},
|
||||
}},
|
||||
}},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listbanned", "")
|
||||
+ HelpExampleRpc("listbanned", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
if(!node.banman) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded");
|
||||
}
|
||||
|
||||
banmap_t banMap;
|
||||
node.banman->GetBanned(banMap);
|
||||
const int64_t current_time{GetTime()};
|
||||
|
||||
UniValue bannedAddresses(UniValue::VARR);
|
||||
for (const auto& entry : banMap)
|
||||
{
|
||||
const CBanEntry& banEntry = entry.second;
|
||||
UniValue rec(UniValue::VOBJ);
|
||||
rec.pushKV("address", entry.first.ToString());
|
||||
rec.pushKV("ban_created", banEntry.nCreateTime);
|
||||
rec.pushKV("banned_until", banEntry.nBanUntil);
|
||||
rec.pushKV("ban_duration", (banEntry.nBanUntil - banEntry.nCreateTime));
|
||||
rec.pushKV("time_remaining", (banEntry.nBanUntil - current_time));
|
||||
|
||||
bannedAddresses.push_back(rec);
|
||||
}
|
||||
|
||||
return bannedAddresses;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan clearbanned()
|
||||
{
|
||||
return RPCHelpMan{"clearbanned",
|
||||
"\nClear all banned IPs.\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("clearbanned", "")
|
||||
+ HelpExampleRpc("clearbanned", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
if (!node.banman) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded");
|
||||
}
|
||||
|
||||
node.banman->ClearBanned();
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan setnetworkactive()
|
||||
{
|
||||
return RPCHelpMan{"setnetworkactive",
|
||||
"\nDisable/enable all p2p network activity.\n",
|
||||
{
|
||||
{"state", RPCArg::Type::BOOL, RPCArg::Optional::NO, "true to enable networking, false to disable"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::BOOL, "", "The value that was passed in"},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
|
||||
connman.SetNetworkActive(request.params[0].get_bool());
|
||||
|
||||
return connman.GetNetworkActive();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getnodeaddresses()
|
||||
{
|
||||
return RPCHelpMan{"getnodeaddresses",
|
||||
"\nReturn known addresses, which can potentially be used to find new nodes in the network.\n",
|
||||
{
|
||||
{"count", RPCArg::Type::NUM, RPCArg::Default{1}, "The maximum number of addresses to return. Specify 0 to return all known addresses."},
|
||||
{"network", RPCArg::Type::STR, RPCArg::DefaultHint{"all networks"}, "Return only addresses of the specified network. Can be one of: " + Join(GetNetworkNames(), ", ") + "."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::NUM_TIME, "time", "The " + UNIX_EPOCH_TIME + " when the node was last seen"},
|
||||
{RPCResult::Type::NUM, "services", "The services offered by the node"},
|
||||
{RPCResult::Type::STR, "address", "The address of the node"},
|
||||
{RPCResult::Type::NUM, "port", "The port number of the node"},
|
||||
{RPCResult::Type::STR, "network", "The network (" + Join(GetNetworkNames(), ", ") + ") the node connected through"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getnodeaddresses", "8")
|
||||
+ HelpExampleCli("getnodeaddresses", "4 \"i2p\"")
|
||||
+ HelpExampleCli("-named getnodeaddresses", "network=onion count=12")
|
||||
+ HelpExampleRpc("getnodeaddresses", "8")
|
||||
+ HelpExampleRpc("getnodeaddresses", "4, \"i2p\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CConnman& connman = EnsureConnman(node);
|
||||
|
||||
const int count{request.params[0].isNull() ? 1 : request.params[0].get_int()};
|
||||
if (count < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range");
|
||||
|
||||
const std::optional<Network> network{request.params[1].isNull() ? std::nullopt : std::optional<Network>{ParseNetwork(request.params[1].get_str())}};
|
||||
if (network == NET_UNROUTABLE) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Network not recognized: %s", request.params[1].get_str()));
|
||||
}
|
||||
|
||||
// returns a shuffled list of CAddress
|
||||
const std::vector<CAddress> vAddr{connman.GetAddresses(count, /* max_pct */ 0, network)};
|
||||
UniValue ret(UniValue::VARR);
|
||||
|
||||
for (const CAddress& addr : vAddr) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("time", (int)addr.nTime);
|
||||
obj.pushKV("services", (uint64_t)addr.nServices);
|
||||
obj.pushKV("address", addr.ToStringIP());
|
||||
obj.pushKV("port", addr.GetPort());
|
||||
obj.pushKV("network", GetNetworkName(addr.GetNetClass()));
|
||||
ret.push_back(obj);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan addpeeraddress()
|
||||
{
|
||||
return RPCHelpMan{"addpeeraddress",
|
||||
"\nAdd the address of a potential peer to the address manager. This RPC is for testing only.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address of the peer"},
|
||||
{"port", RPCArg::Type::NUM, RPCArg::Optional::NO, "The port of the peer"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "success", "whether the peer address was successfully added to the address manager"},
|
||||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 12024")
|
||||
+ HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 12024")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
if (!node.addrman) {
|
||||
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Address manager functionality missing or disabled");
|
||||
}
|
||||
|
||||
const std::string& addr_string{request.params[0].get_str()};
|
||||
const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())};
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CNetAddr net_addr;
|
||||
bool success{false};
|
||||
|
||||
if (LookupHost(addr_string, net_addr, false)) {
|
||||
CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}};
|
||||
address.nTime = GetAdjustedTime();
|
||||
// The source address is set equal to the address. This is equivalent to the peer
|
||||
// announcing itself.
|
||||
if (node.addrman->Add(address, address)) success = true;
|
||||
}
|
||||
|
||||
obj.pushKV("success", success);
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void RegisterNetRPCCommands(CRPCTable &t)
|
||||
{
|
||||
// clang-format off
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category actor
|
||||
// --------------------- -----------------------
|
||||
{ "network", &getconnectioncount, },
|
||||
{ "network", &ping, },
|
||||
{ "network", &getpeerinfo, },
|
||||
{ "network", &addnode, },
|
||||
{ "network", &disconnectnode, },
|
||||
{ "network", &getaddednodeinfo, },
|
||||
{ "network", &getnettotals, },
|
||||
{ "network", &getnetworkinfo, },
|
||||
{ "network", &setban, },
|
||||
{ "network", &listbanned, },
|
||||
{ "network", &clearbanned, },
|
||||
{ "network", &setnetworkactive, },
|
||||
{ "network", &getnodeaddresses, },
|
||||
|
||||
{ "hidden", &addconnection, },
|
||||
{ "hidden", &addpeeraddress, },
|
||||
};
|
||||
// clang-format on
|
||||
for (const auto& c : commands) {
|
||||
t.appendCommand(c.name, &c);
|
||||
}
|
||||
}
|
||||
15
src/rpc/net.h
Normal file
15
src/rpc/net.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2021 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_NET_H
|
||||
#define DIGIBYTE_RPC_NET_H
|
||||
|
||||
class CConnman;
|
||||
class PeerManager;
|
||||
struct NodeContext;
|
||||
|
||||
CConnman& EnsureConnman(const NodeContext& node);
|
||||
PeerManager& EnsurePeerman(const NodeContext& node);
|
||||
|
||||
#endif // DIGIBYTE_RPC_NET_H
|
||||
92
src/rpc/protocol.h
Normal file
92
src/rpc/protocol.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2009-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_PROTOCOL_H
|
||||
#define DIGIBYTE_RPC_PROTOCOL_H
|
||||
|
||||
//! 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,
|
||||
};
|
||||
|
||||
//! DigiByte 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,
|
||||
// RPC_INTERNAL_ERROR should only be used for genuine errors in vextad
|
||||
// (for example datadir corruption).
|
||||
RPC_INTERNAL_ERROR = -32603,
|
||||
RPC_PARSE_ERROR = -32700,
|
||||
|
||||
//! General application defined errors
|
||||
RPC_MISC_ERROR = -1, //!< std::exception thrown in command handling
|
||||
RPC_TYPE_ERROR = -3, //!< Unexpected type was passed as parameter
|
||||
RPC_INVALID_ADDRESS_OR_KEY = -5, //!< Invalid address or key
|
||||
RPC_OUT_OF_MEMORY = -7, //!< Ran out of memory during operation
|
||||
RPC_INVALID_PARAMETER = -8, //!< Invalid, missing or duplicate parameter
|
||||
RPC_DATABASE_ERROR = -20, //!< Database error
|
||||
RPC_DESERIALIZATION_ERROR = -22, //!< Error parsing or validating structure in raw format
|
||||
RPC_VERIFY_ERROR = -25, //!< General error during transaction or block submission
|
||||
RPC_VERIFY_REJECTED = -26, //!< Transaction or block was rejected by network rules
|
||||
RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
|
||||
RPC_IN_WARMUP = -28, //!< Client still warming up
|
||||
RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated
|
||||
|
||||
//! Aliases for backward compatibility
|
||||
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
|
||||
RPC_TRANSACTION_REJECTED = RPC_VERIFY_REJECTED,
|
||||
RPC_TRANSACTION_ALREADY_IN_CHAIN= RPC_VERIFY_ALREADY_IN_CHAIN,
|
||||
|
||||
//! P2P client errors
|
||||
RPC_CLIENT_NOT_CONNECTED = -9, //!< DigiByte is not connected
|
||||
RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, //!< Still downloading initial blocks
|
||||
RPC_CLIENT_NODE_ALREADY_ADDED = -23, //!< Node is already added
|
||||
RPC_CLIENT_NODE_NOT_ADDED = -24, //!< Node has not been added before
|
||||
RPC_CLIENT_NODE_NOT_CONNECTED = -29, //!< Node to disconnect not found in connected nodes
|
||||
RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //!< Invalid IP/Subnet
|
||||
RPC_CLIENT_P2P_DISABLED = -31, //!< No valid connection manager instance found
|
||||
RPC_CLIENT_NODE_CAPACITY_REACHED= -34, //!< Max number of outbound or block-relay connections already open
|
||||
|
||||
//! Chain errors
|
||||
RPC_CLIENT_MEMPOOL_DISABLED = -33, //!< No mempool instance found
|
||||
|
||||
//! Wallet errors
|
||||
RPC_WALLET_ERROR = -4, //!< Unspecified problem with wallet (key not found etc.)
|
||||
RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Not enough funds in wallet or account
|
||||
RPC_WALLET_INVALID_LABEL_NAME = -11, //!< Invalid label name
|
||||
RPC_WALLET_KEYPOOL_RAN_OUT = -12, //!< Keypool ran out, call keypoolrefill first
|
||||
RPC_WALLET_UNLOCK_NEEDED = -13, //!< Enter the wallet passphrase with walletpassphrase first
|
||||
RPC_WALLET_PASSPHRASE_INCORRECT = -14, //!< The wallet passphrase entered was incorrect
|
||||
RPC_WALLET_WRONG_ENC_STATE = -15, //!< Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
|
||||
RPC_WALLET_ENCRYPTION_FAILED = -16, //!< Failed to encrypt the wallet
|
||||
RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked
|
||||
RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified
|
||||
RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded)
|
||||
RPC_WALLET_ALREADY_LOADED = -35, //!< This same wallet is already loaded
|
||||
|
||||
//! Backwards compatible aliases
|
||||
RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME,
|
||||
|
||||
//! Unused reserved codes, kept around for backwards compatibility. Do not reuse.
|
||||
RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode
|
||||
};
|
||||
|
||||
#endif // DIGIBYTE_RPC_PROTOCOL_H
|
||||
1928
src/rpc/rawtransaction.cpp
Normal file
1928
src/rpc/rawtransaction.cpp
Normal file
File diff suppressed because it is too large
Load diff
19
src/rpc/rawtransaction.h
Normal file
19
src/rpc/rawtransaction.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2019 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_RAWTRANSACTION_H
|
||||
#define DIGIBYTE_RPC_RAWTRANSACTION_H
|
||||
|
||||
class CBasicKeyStore;
|
||||
struct CMutableTransaction;
|
||||
class UniValue;
|
||||
|
||||
/** Sign a transaction with the given keystore and previous transactions */
|
||||
UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
|
||||
|
||||
/** Create a transaction from univalue parameters */
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf);
|
||||
|
||||
#endif // DIGIBYTE_RPC_RAWTRANSACTION_H
|
||||
309
src/rpc/rawtransaction_util.cpp
Normal file
309
src/rpc/rawtransaction_util.cpp
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <rpc/rawtransaction_util.h>
|
||||
|
||||
#include <coins.h>
|
||||
#include <core_io.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <rpc/request.h>
|
||||
#include <rpc/util.h>
|
||||
#include <script/sign.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <tinyformat.h>
|
||||
#include <univalue.h>
|
||||
#include <util/rbf.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf)
|
||||
{
|
||||
if (outputs_in.isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
|
||||
}
|
||||
|
||||
UniValue inputs;
|
||||
if (inputs_in.isNull()) {
|
||||
inputs = UniValue::VARR;
|
||||
} else {
|
||||
inputs = inputs_in.get_array();
|
||||
}
|
||||
|
||||
const bool outputs_is_obj = outputs_in.isObject();
|
||||
UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
|
||||
|
||||
CMutableTransaction rawTx;
|
||||
|
||||
if (!locktime.isNull()) {
|
||||
int64_t nLockTime = locktime.get_int64();
|
||||
if (nLockTime < 0 || nLockTime > LOCKTIME_MAX)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
|
||||
rawTx.nLockTime = nLockTime;
|
||||
}
|
||||
|
||||
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
|
||||
const UniValue& input = inputs[idx];
|
||||
const UniValue& o = input.get_obj();
|
||||
|
||||
uint256 txid = ParseHashO(o, "txid");
|
||||
|
||||
const UniValue& vout_v = find_value(o, "vout");
|
||||
if (!vout_v.isNum())
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
|
||||
int nOutput = vout_v.get_int();
|
||||
if (nOutput < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
|
||||
|
||||
uint32_t nSequence;
|
||||
if (rbf) {
|
||||
nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
|
||||
} else if (rawTx.nLockTime) {
|
||||
nSequence = CTxIn::SEQUENCE_FINAL - 1;
|
||||
} else {
|
||||
nSequence = CTxIn::SEQUENCE_FINAL;
|
||||
}
|
||||
|
||||
// set the sequence number if passed in the parameters object
|
||||
const UniValue& sequenceObj = find_value(o, "sequence");
|
||||
if (sequenceObj.isNum()) {
|
||||
int64_t seqNr64 = sequenceObj.get_int64();
|
||||
if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
|
||||
} else {
|
||||
nSequence = (uint32_t)seqNr64;
|
||||
}
|
||||
}
|
||||
|
||||
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
|
||||
|
||||
rawTx.vin.push_back(in);
|
||||
}
|
||||
|
||||
if (!outputs_is_obj) {
|
||||
// Translate array of key-value pairs into dict
|
||||
UniValue outputs_dict = UniValue(UniValue::VOBJ);
|
||||
for (size_t i = 0; i < outputs.size(); ++i) {
|
||||
const UniValue& output = outputs[i];
|
||||
if (!output.isObject()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected");
|
||||
}
|
||||
if (output.size() != 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key");
|
||||
}
|
||||
outputs_dict.pushKVs(output);
|
||||
}
|
||||
outputs = std::move(outputs_dict);
|
||||
}
|
||||
|
||||
// Duplicate checking
|
||||
std::set<CTxDestination> destinations;
|
||||
bool has_data{false};
|
||||
|
||||
for (const std::string& name_ : outputs.getKeys()) {
|
||||
if (name_ == "data") {
|
||||
if (has_data) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, duplicate key: data");
|
||||
}
|
||||
has_data = true;
|
||||
std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");
|
||||
|
||||
CTxOut out(0, CScript() << OP_RETURN << data);
|
||||
rawTx.vout.push_back(out);
|
||||
} else {
|
||||
CTxDestination destination = DecodeDestination(name_);
|
||||
if (!IsValidDestination(destination)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Vexta address: ") + name_);
|
||||
}
|
||||
|
||||
if (!destinations.insert(destination).second) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
|
||||
}
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(destination);
|
||||
CAmount nAmount = AmountFromValue(outputs[name_]);
|
||||
|
||||
CTxOut out(nAmount, scriptPubKey);
|
||||
rawTx.vout.push_back(out);
|
||||
}
|
||||
}
|
||||
|
||||
if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
|
||||
}
|
||||
|
||||
return rawTx;
|
||||
}
|
||||
|
||||
/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
|
||||
static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage)
|
||||
{
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("txid", txin.prevout.hash.ToString());
|
||||
entry.pushKV("vout", (uint64_t)txin.prevout.n);
|
||||
UniValue witness(UniValue::VARR);
|
||||
for (unsigned int i = 0; i < txin.scriptWitness.stack.size(); i++) {
|
||||
witness.push_back(HexStr(txin.scriptWitness.stack[i]));
|
||||
}
|
||||
entry.pushKV("witness", witness);
|
||||
entry.pushKV("scriptSig", HexStr(txin.scriptSig));
|
||||
entry.pushKV("sequence", (uint64_t)txin.nSequence);
|
||||
entry.pushKV("error", strMessage);
|
||||
vErrorsRet.push_back(entry);
|
||||
}
|
||||
|
||||
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
|
||||
{
|
||||
if (!prevTxsUnival.isNull()) {
|
||||
UniValue prevTxs = prevTxsUnival.get_array();
|
||||
for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) {
|
||||
const UniValue& p = prevTxs[idx];
|
||||
if (!p.isObject()) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}");
|
||||
}
|
||||
|
||||
UniValue prevOut = p.get_obj();
|
||||
|
||||
RPCTypeCheckObj(prevOut,
|
||||
{
|
||||
{"txid", UniValueType(UniValue::VSTR)},
|
||||
{"vout", UniValueType(UniValue::VNUM)},
|
||||
{"scriptPubKey", UniValueType(UniValue::VSTR)},
|
||||
});
|
||||
|
||||
uint256 txid = ParseHashO(prevOut, "txid");
|
||||
|
||||
int nOut = find_value(prevOut, "vout").get_int();
|
||||
if (nOut < 0) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout cannot be negative");
|
||||
}
|
||||
|
||||
COutPoint out(txid, nOut);
|
||||
std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
|
||||
CScript scriptPubKey(pkData.begin(), pkData.end());
|
||||
|
||||
{
|
||||
auto coin = coins.find(out);
|
||||
if (coin != coins.end() && !coin->second.IsSpent() && coin->second.out.scriptPubKey != scriptPubKey) {
|
||||
std::string err("Previous output scriptPubKey mismatch:\n");
|
||||
err = err + ScriptToAsmStr(coin->second.out.scriptPubKey) + "\nvs:\n"+
|
||||
ScriptToAsmStr(scriptPubKey);
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
|
||||
}
|
||||
Coin newcoin;
|
||||
newcoin.out.scriptPubKey = scriptPubKey;
|
||||
newcoin.out.nValue = MAX_MONEY;
|
||||
if (prevOut.exists("amount")) {
|
||||
newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
|
||||
}
|
||||
newcoin.nHeight = 1;
|
||||
coins[out] = std::move(newcoin);
|
||||
}
|
||||
|
||||
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
|
||||
const bool is_p2sh = scriptPubKey.IsPayToScriptHash();
|
||||
const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash();
|
||||
if (keystore && (is_p2sh || is_p2wsh)) {
|
||||
RPCTypeCheckObj(prevOut,
|
||||
{
|
||||
{"redeemScript", UniValueType(UniValue::VSTR)},
|
||||
{"witnessScript", UniValueType(UniValue::VSTR)},
|
||||
}, true);
|
||||
UniValue rs = find_value(prevOut, "redeemScript");
|
||||
UniValue ws = find_value(prevOut, "witnessScript");
|
||||
if (rs.isNull() && ws.isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript");
|
||||
}
|
||||
|
||||
// work from witnessScript when possible
|
||||
std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript"));
|
||||
CScript script(scriptData.begin(), scriptData.end());
|
||||
keystore->AddCScript(script);
|
||||
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
|
||||
// This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
|
||||
CScript witness_output_script{GetScriptForDestination(WitnessV0ScriptHash(script))};
|
||||
keystore->AddCScript(witness_output_script);
|
||||
|
||||
if (!ws.isNull() && !rs.isNull()) {
|
||||
// if both witnessScript and redeemScript are provided,
|
||||
// they should either be the same (for backwards compat),
|
||||
// or the redeemScript should be the encoded form of
|
||||
// the witnessScript (ie, for p2sh-p2wsh)
|
||||
if (ws.get_str() != rs.get_str()) {
|
||||
std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript"));
|
||||
CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end());
|
||||
if (redeemScript != witness_output_script) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_p2sh) {
|
||||
const CTxDestination p2sh{ScriptHash(script)};
|
||||
const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)};
|
||||
if (scriptPubKey == GetScriptForDestination(p2sh)) {
|
||||
// traditional p2sh; arguably an error if
|
||||
// we got here with rs.IsNull(), because
|
||||
// that means the p2sh script was specified
|
||||
// via witnessScript param, but for now
|
||||
// we'll just quietly accept it
|
||||
} else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) {
|
||||
// p2wsh encoded as p2sh; ideally the witness
|
||||
// script was specified in the witnessScript
|
||||
// param, but also support specifying it via
|
||||
// redeemScript param for backwards compat
|
||||
// (in which case ws.IsNull() == true)
|
||||
} else {
|
||||
// otherwise, can't generate scriptPubKey from
|
||||
// either script, so we got unusable parameters
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
|
||||
}
|
||||
} else if (is_p2wsh) {
|
||||
// plain p2wsh; could throw an error if script
|
||||
// was specified by redeemScript rather than
|
||||
// witnessScript (ie, ws.IsNull() == true), but
|
||||
// accept it for backwards compat
|
||||
const CTxDestination p2wsh{WitnessV0ScriptHash(script)};
|
||||
if (scriptPubKey != GetScriptForDestination(p2wsh)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
|
||||
{
|
||||
int nHashType = ParseSighashString(hashType);
|
||||
|
||||
// Script verification errors
|
||||
std::map<int, std::string> input_errors;
|
||||
|
||||
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors);
|
||||
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
|
||||
}
|
||||
|
||||
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result)
|
||||
{
|
||||
// Make errors UniValue
|
||||
UniValue vErrors(UniValue::VARR);
|
||||
for (const auto& err_pair : input_errors) {
|
||||
if (err_pair.second == "Missing amount") {
|
||||
// This particular error needs to be an exception for some reason
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
|
||||
}
|
||||
TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
|
||||
}
|
||||
|
||||
result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
|
||||
result.pushKV("complete", complete);
|
||||
if (!vErrors.empty()) {
|
||||
if (result.exists("errors")) {
|
||||
vErrors.push_backV(result["errors"].getValues());
|
||||
}
|
||||
result.pushKV("errors", vErrors);
|
||||
}
|
||||
}
|
||||
42
src/rpc/rawtransaction_util.h
Normal file
42
src/rpc/rawtransaction_util.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2017-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_RAWTRANSACTION_UTIL_H
|
||||
#define DIGIBYTE_RPC_RAWTRANSACTION_UTIL_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class FillableSigningProvider;
|
||||
class UniValue;
|
||||
struct CMutableTransaction;
|
||||
class Coin;
|
||||
class COutPoint;
|
||||
class SigningProvider;
|
||||
|
||||
/**
|
||||
* Sign a transaction with the given keystore and previous transactions
|
||||
*
|
||||
* @param mtx The transaction to-be-signed
|
||||
* @param keystore Temporary keystore containing signing keys
|
||||
* @param coins Map of unspent outputs
|
||||
* @param hashType The signature hash type
|
||||
* @param result JSON object where signed transaction results accumulate
|
||||
*/
|
||||
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
|
||||
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, const std::map<int, std::string>& input_errors, UniValue& result);
|
||||
|
||||
/**
|
||||
* Parse a prevtxs UniValue array and get the map of coins from it
|
||||
*
|
||||
* @param prevTxsUnival Array of previous txns outputs that tx depends on but may not yet be in the block chain
|
||||
* @param keystore A pointer to the temporary keystore if there is one
|
||||
* @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call
|
||||
*/
|
||||
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
|
||||
|
||||
/** Create a transaction from univalue parameters */
|
||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf);
|
||||
|
||||
#endif // DIGIBYTE_RPC_RAWTRANSACTION_UTIL_H
|
||||
37
src/rpc/register.h
Normal file
37
src/rpc/register.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2009-2018 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_REGISTER_H
|
||||
#define DIGIBYTE_RPC_REGISTER_H
|
||||
|
||||
/** These are in one header file to avoid creating tons of single-function
|
||||
* headers for everything under src/rpc/ */
|
||||
class CRPCTable;
|
||||
|
||||
/** Register block chain RPC commands */
|
||||
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
|
||||
/** Register P2P networking RPC commands */
|
||||
void RegisterNetRPCCommands(CRPCTable &tableRPC);
|
||||
/** Register miscellaneous RPC commands */
|
||||
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
|
||||
/** Register mining RPC commands */
|
||||
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
|
||||
/** Register raw transaction RPC commands */
|
||||
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
|
||||
/** Register raw transaction RPC commands */
|
||||
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
|
||||
|
||||
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
|
||||
{
|
||||
RegisterBlockchainRPCCommands(t);
|
||||
RegisterNetRPCCommands(t);
|
||||
RegisterMiscRPCCommands(t);
|
||||
RegisterMiningRPCCommands(t);
|
||||
RegisterRawTransactionRPCCommands(t);
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
RegisterSignerRPCCommands(t);
|
||||
#endif // ENABLE_EXTERNAL_SIGNER
|
||||
}
|
||||
|
||||
#endif // DIGIBYTE_RPC_REGISTER_H
|
||||
185
src/rpc/request.cpp
Normal file
185
src/rpc/request.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <rpc/request.h>
|
||||
|
||||
#include <fs.h>
|
||||
|
||||
#include <random.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <util/system.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
/**
|
||||
* JSON-RPC protocol. DigiByte speaks version 1.0 for maximum compatibility,
|
||||
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
|
||||
* unspecified (HTTP errors and contents of 'error').
|
||||
*
|
||||
* 1.0 spec: http://json-rpc.org/wiki/specification
|
||||
* 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html
|
||||
*/
|
||||
|
||||
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id)
|
||||
{
|
||||
UniValue request(UniValue::VOBJ);
|
||||
request.pushKV("method", strMethod);
|
||||
request.pushKV("params", params);
|
||||
request.pushKV("id", id);
|
||||
return request;
|
||||
}
|
||||
|
||||
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id)
|
||||
{
|
||||
UniValue reply(UniValue::VOBJ);
|
||||
if (!error.isNull())
|
||||
reply.pushKV("result", NullUniValue);
|
||||
else
|
||||
reply.pushKV("result", result);
|
||||
reply.pushKV("error", error);
|
||||
reply.pushKV("id", id);
|
||||
return reply;
|
||||
}
|
||||
|
||||
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id)
|
||||
{
|
||||
UniValue reply = JSONRPCReplyObj(result, error, id);
|
||||
return reply.write() + "\n";
|
||||
}
|
||||
|
||||
UniValue JSONRPCError(int code, const std::string& message)
|
||||
{
|
||||
UniValue error(UniValue::VOBJ);
|
||||
error.pushKV("code", code);
|
||||
error.pushKV("message", message);
|
||||
return error;
|
||||
}
|
||||
|
||||
/** Username used when cookie authentication is in use (arbitrary, only for
|
||||
* recognizability in debugging/logging purposes)
|
||||
*/
|
||||
static const std::string COOKIEAUTH_USER = "__cookie__";
|
||||
/** Default name for auth cookie file */
|
||||
static const std::string COOKIEAUTH_FILE = ".cookie";
|
||||
|
||||
/** Get name of RPC authentication cookie file */
|
||||
static fs::path GetAuthCookieFile(bool temp=false)
|
||||
{
|
||||
std::string arg = gArgs.GetArg("-rpccookiefile", COOKIEAUTH_FILE);
|
||||
if (temp) {
|
||||
arg += ".tmp";
|
||||
}
|
||||
return AbsPathForConfigVal(fs::path(arg));
|
||||
}
|
||||
|
||||
bool GenerateAuthCookie(std::string *cookie_out)
|
||||
{
|
||||
const size_t COOKIE_SIZE = 32;
|
||||
unsigned char rand_pwd[COOKIE_SIZE];
|
||||
GetRandBytes(rand_pwd, COOKIE_SIZE);
|
||||
std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd);
|
||||
|
||||
/** the umask determines what permissions are used to create this file -
|
||||
* these are set to 077 in init.cpp unless overridden with -sysperms.
|
||||
*/
|
||||
fsbridge::ofstream file;
|
||||
fs::path filepath_tmp = GetAuthCookieFile(true);
|
||||
file.open(filepath_tmp);
|
||||
if (!file.is_open()) {
|
||||
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string());
|
||||
return false;
|
||||
}
|
||||
file << cookie;
|
||||
file.close();
|
||||
|
||||
fs::path filepath = GetAuthCookieFile(false);
|
||||
if (!RenameOver(filepath_tmp, filepath)) {
|
||||
LogPrintf("Unable to rename cookie authentication file %s to %s\n", filepath_tmp.string(), filepath.string());
|
||||
return false;
|
||||
}
|
||||
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
|
||||
|
||||
if (cookie_out)
|
||||
*cookie_out = cookie;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAuthCookie(std::string *cookie_out)
|
||||
{
|
||||
fsbridge::ifstream file;
|
||||
std::string cookie;
|
||||
fs::path filepath = GetAuthCookieFile();
|
||||
file.open(filepath);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
std::getline(file, cookie);
|
||||
file.close();
|
||||
|
||||
if (cookie_out)
|
||||
*cookie_out = cookie;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeleteAuthCookie()
|
||||
{
|
||||
try {
|
||||
fs::remove(GetAuthCookieFile());
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, fsbridge::get_filesystem_error_message(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
|
||||
{
|
||||
if (!in.isArray()) {
|
||||
throw std::runtime_error("Batch must be an array");
|
||||
}
|
||||
const size_t num {in.size()};
|
||||
std::vector<UniValue> batch(num);
|
||||
for (const UniValue& rec : in.getValues()) {
|
||||
if (!rec.isObject()) {
|
||||
throw std::runtime_error("Batch member must be an object");
|
||||
}
|
||||
size_t id = rec["id"].get_int();
|
||||
if (id >= num) {
|
||||
throw std::runtime_error("Batch member id is larger than batch size");
|
||||
}
|
||||
batch[id] = rec;
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
|
||||
void JSONRPCRequest::parse(const UniValue& valRequest)
|
||||
{
|
||||
// Parse request
|
||||
if (!valRequest.isObject())
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object");
|
||||
const UniValue& request = valRequest.get_obj();
|
||||
|
||||
// Parse id now so errors from here on will have the id
|
||||
id = find_value(request, "id");
|
||||
|
||||
// Parse method
|
||||
UniValue valMethod = find_value(request, "method");
|
||||
if (valMethod.isNull())
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method");
|
||||
if (!valMethod.isStr())
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
|
||||
strMethod = valMethod.get_str();
|
||||
if (fLogIPs)
|
||||
LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod),
|
||||
this->authUser, this->peerAddr);
|
||||
else
|
||||
LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser);
|
||||
|
||||
// Parse params
|
||||
UniValue valParams = find_value(request, "params");
|
||||
if (valParams.isArray() || valParams.isObject())
|
||||
params = valParams;
|
||||
else if (valParams.isNull())
|
||||
params = UniValue(UniValue::VARR);
|
||||
else
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
|
||||
}
|
||||
43
src/rpc/request.h
Normal file
43
src/rpc/request.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_REQUEST_H
|
||||
#define DIGIBYTE_RPC_REQUEST_H
|
||||
|
||||
#include <any>
|
||||
#include <string>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
|
||||
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id);
|
||||
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
|
||||
UniValue JSONRPCError(int code, const std::string& message);
|
||||
|
||||
/** Generate a new RPC authentication cookie and write it to disk */
|
||||
bool GenerateAuthCookie(std::string *cookie_out);
|
||||
/** Read the RPC authentication cookie from disk */
|
||||
bool GetAuthCookie(std::string *cookie_out);
|
||||
/** Delete RPC authentication cookie from disk */
|
||||
void DeleteAuthCookie();
|
||||
/** Parse JSON-RPC batch reply into a vector */
|
||||
std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in);
|
||||
|
||||
class JSONRPCRequest
|
||||
{
|
||||
public:
|
||||
UniValue id;
|
||||
std::string strMethod;
|
||||
UniValue params;
|
||||
enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
|
||||
std::string URI;
|
||||
std::string authUser;
|
||||
std::string peerAddr;
|
||||
std::any context;
|
||||
|
||||
void parse(const UniValue& valRequest);
|
||||
};
|
||||
|
||||
#endif // DIGIBYTE_RPC_REQUEST_H
|
||||
12
src/rpc/safemode.h
Normal file
12
src/rpc/safemode.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2017 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_SAFEMODE_H
|
||||
#define DIGIBYTE_RPC_SAFEMODE_H
|
||||
|
||||
static const bool DEFAULT_DISABLE_SAFEMODE = true;
|
||||
|
||||
void ObserveSafeMode();
|
||||
|
||||
#endif // DIGIBYTE_RPC_SAFEMODE_H
|
||||
549
src/rpc/server.cpp
Normal file
549
src/rpc/server.cpp
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <rpc/server.h>
|
||||
|
||||
#include <rpc/util.h>
|
||||
#include <shutdown.h>
|
||||
#include <sync.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/signals2/signal.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory> // for unique_ptr
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
static Mutex g_rpc_warmup_mutex;
|
||||
static std::atomic<bool> g_rpc_running{false};
|
||||
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
||||
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
||||
/* Timer-creating functions */
|
||||
static RPCTimerInterface* timerInterface = nullptr;
|
||||
/* Map of name to timer. */
|
||||
static Mutex g_deadline_timers_mutex;
|
||||
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
|
||||
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
|
||||
|
||||
struct RPCCommandExecutionInfo
|
||||
{
|
||||
std::string method;
|
||||
int64_t start;
|
||||
};
|
||||
|
||||
struct RPCServerInfo
|
||||
{
|
||||
Mutex mutex;
|
||||
std::list<RPCCommandExecutionInfo> active_commands GUARDED_BY(mutex);
|
||||
};
|
||||
|
||||
static RPCServerInfo g_rpc_server_info;
|
||||
|
||||
struct RPCCommandExecution
|
||||
{
|
||||
std::list<RPCCommandExecutionInfo>::iterator it;
|
||||
explicit RPCCommandExecution(const std::string& method)
|
||||
{
|
||||
LOCK(g_rpc_server_info.mutex);
|
||||
it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, GetTimeMicros()});
|
||||
}
|
||||
~RPCCommandExecution()
|
||||
{
|
||||
LOCK(g_rpc_server_info.mutex);
|
||||
g_rpc_server_info.active_commands.erase(it);
|
||||
}
|
||||
};
|
||||
|
||||
static struct CRPCSignals
|
||||
{
|
||||
boost::signals2::signal<void ()> Started;
|
||||
boost::signals2::signal<void ()> Stopped;
|
||||
} g_rpcSignals;
|
||||
|
||||
void RPCServer::OnStarted(std::function<void ()> slot)
|
||||
{
|
||||
g_rpcSignals.Started.connect(slot);
|
||||
}
|
||||
|
||||
void RPCServer::OnStopped(std::function<void ()> slot)
|
||||
{
|
||||
g_rpcSignals.Stopped.connect(slot);
|
||||
}
|
||||
|
||||
std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& helpreq) const
|
||||
{
|
||||
std::string strRet;
|
||||
std::string category;
|
||||
std::set<intptr_t> setDone;
|
||||
std::vector<std::pair<std::string, const CRPCCommand*> > vCommands;
|
||||
|
||||
for (const auto& entry : mapCommands)
|
||||
vCommands.push_back(make_pair(entry.second.front()->category + entry.first, entry.second.front()));
|
||||
sort(vCommands.begin(), vCommands.end());
|
||||
|
||||
JSONRPCRequest jreq = helpreq;
|
||||
jreq.mode = JSONRPCRequest::GET_HELP;
|
||||
jreq.params = UniValue();
|
||||
|
||||
for (const std::pair<std::string, const CRPCCommand*>& command : vCommands)
|
||||
{
|
||||
const CRPCCommand *pcmd = command.second;
|
||||
std::string strMethod = pcmd->name;
|
||||
if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand)
|
||||
continue;
|
||||
jreq.strMethod = strMethod;
|
||||
try
|
||||
{
|
||||
UniValue unused_result;
|
||||
if (setDone.insert(pcmd->unique_id).second)
|
||||
pcmd->actor(jreq, unused_result, true /* last_handler */);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// Help text is returned in an exception
|
||||
std::string strHelp = std::string(e.what());
|
||||
if (strCommand == "")
|
||||
{
|
||||
if (strHelp.find('\n') != std::string::npos)
|
||||
strHelp = strHelp.substr(0, strHelp.find('\n'));
|
||||
|
||||
if (category != pcmd->category)
|
||||
{
|
||||
if (!category.empty())
|
||||
strRet += "\n";
|
||||
category = pcmd->category;
|
||||
strRet += "== " + Capitalize(category) + " ==\n";
|
||||
}
|
||||
}
|
||||
strRet += strHelp + "\n";
|
||||
}
|
||||
}
|
||||
if (strRet == "")
|
||||
strRet = strprintf("help: unknown command: %s\n", strCommand);
|
||||
strRet = strRet.substr(0,strRet.size()-1);
|
||||
return strRet;
|
||||
}
|
||||
|
||||
static RPCHelpMan help()
|
||||
{
|
||||
return RPCHelpMan{"help",
|
||||
"\nList all commands, or get help for a specified command.\n",
|
||||
{
|
||||
{"command", RPCArg::Type::STR, RPCArg::DefaultHint{"all commands"}, "The command to get help on"},
|
||||
},
|
||||
{
|
||||
RPCResult{RPCResult::Type::STR, "", "The help text"},
|
||||
RPCResult{RPCResult::Type::ANY, "", ""},
|
||||
},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
|
||||
{
|
||||
std::string strCommand;
|
||||
if (jsonRequest.params.size() > 0) {
|
||||
strCommand = jsonRequest.params[0].get_str();
|
||||
}
|
||||
if (strCommand == "dump_all_command_conversions") {
|
||||
// Used for testing only, undocumented
|
||||
return tableRPC.dumpArgMap(jsonRequest);
|
||||
}
|
||||
|
||||
return tableRPC.help(strCommand, jsonRequest);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan stop()
|
||||
{
|
||||
static const std::string RESULT{PACKAGE_NAME " stopping"};
|
||||
return RPCHelpMan{"stop",
|
||||
// Also accept the hidden 'wait' integer argument (milliseconds)
|
||||
// For instance, 'stop 1000' makes the call wait 1 second before returning
|
||||
// to the client (intended for testing)
|
||||
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
|
||||
{
|
||||
{"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
|
||||
},
|
||||
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
|
||||
{
|
||||
// Event loop will exit after current HTTP requests have been handled, so
|
||||
// this reply will get back to the client.
|
||||
StartShutdown();
|
||||
if (jsonRequest.params[0].isNum()) {
|
||||
UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()});
|
||||
}
|
||||
return RESULT;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan uptime()
|
||||
{
|
||||
return RPCHelpMan{"uptime",
|
||||
"\nReturns the total uptime of the server.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::NUM, "", "The number of seconds that the server has been running"
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("uptime", "")
|
||||
+ HelpExampleRpc("uptime", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
return GetTime() - GetStartupTime();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan getrpcinfo()
|
||||
{
|
||||
return RPCHelpMan{"getrpcinfo",
|
||||
"\nReturns details of the RPC server.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR, "active_commands", "All active commands",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "Information about an active command",
|
||||
{
|
||||
{RPCResult::Type::STR, "method", "The name of the RPC command"},
|
||||
{RPCResult::Type::NUM, "duration", "The running time in microseconds"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::STR, "logpath", "The complete file path to the debug log"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getrpcinfo", "")
|
||||
+ HelpExampleRpc("getrpcinfo", "")},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
LOCK(g_rpc_server_info.mutex);
|
||||
UniValue active_commands(UniValue::VARR);
|
||||
for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("method", info.method);
|
||||
entry.pushKV("duration", GetTimeMicros() - info.start);
|
||||
active_commands.push_back(entry);
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("active_commands", active_commands);
|
||||
|
||||
const std::string path = LogInstance().m_file_path.string();
|
||||
UniValue log_path(UniValue::VSTR, path);
|
||||
result.pushKV("logpath", log_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static const CRPCCommand vRPCCommands[] =
|
||||
{ // category actor (function)
|
||||
// --------------------- -----------------------
|
||||
/* Overall control/query calls */
|
||||
{ "control", &getrpcinfo, },
|
||||
{ "control", &help, },
|
||||
{ "control", &stop, },
|
||||
{ "control", &uptime, },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
CRPCTable::CRPCTable()
|
||||
{
|
||||
for (const auto& c : vRPCCommands) {
|
||||
appendCommand(c.name, &c);
|
||||
}
|
||||
}
|
||||
|
||||
void CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd)
|
||||
{
|
||||
CHECK_NONFATAL(!IsRPCRunning()); // Only add commands before rpc is running
|
||||
|
||||
mapCommands[name].push_back(pcmd);
|
||||
}
|
||||
|
||||
bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd)
|
||||
{
|
||||
auto it = mapCommands.find(name);
|
||||
if (it != mapCommands.end()) {
|
||||
auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd);
|
||||
if (it->second.end() != new_end) {
|
||||
it->second.erase(new_end, it->second.end());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void StartRPC()
|
||||
{
|
||||
LogPrint(BCLog::RPC, "Starting RPC\n");
|
||||
g_rpc_running = true;
|
||||
g_rpcSignals.Started();
|
||||
}
|
||||
|
||||
void InterruptRPC()
|
||||
{
|
||||
static std::once_flag g_rpc_interrupt_flag;
|
||||
// This function could be called twice if the GUI has been started with -server=1.
|
||||
std::call_once(g_rpc_interrupt_flag, []() {
|
||||
LogPrint(BCLog::RPC, "Interrupting RPC\n");
|
||||
// Interrupt e.g. running longpolls
|
||||
g_rpc_running = false;
|
||||
});
|
||||
}
|
||||
|
||||
void StopRPC()
|
||||
{
|
||||
static std::once_flag g_rpc_stop_flag;
|
||||
// This function could be called twice if the GUI has been started with -server=1.
|
||||
assert(!g_rpc_running);
|
||||
std::call_once(g_rpc_stop_flag, []() {
|
||||
LogPrint(BCLog::RPC, "Stopping RPC\n");
|
||||
WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
|
||||
DeleteAuthCookie();
|
||||
g_rpcSignals.Stopped();
|
||||
});
|
||||
}
|
||||
|
||||
bool IsRPCRunning()
|
||||
{
|
||||
return g_rpc_running;
|
||||
}
|
||||
|
||||
void RpcInterruptionPoint()
|
||||
{
|
||||
if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
|
||||
}
|
||||
|
||||
void SetRPCWarmupStatus(const std::string& newStatus)
|
||||
{
|
||||
LOCK(g_rpc_warmup_mutex);
|
||||
rpcWarmupStatus = newStatus;
|
||||
}
|
||||
|
||||
void SetRPCWarmupFinished()
|
||||
{
|
||||
LOCK(g_rpc_warmup_mutex);
|
||||
assert(fRPCInWarmup);
|
||||
fRPCInWarmup = false;
|
||||
}
|
||||
|
||||
bool RPCIsInWarmup(std::string *outStatus)
|
||||
{
|
||||
LOCK(g_rpc_warmup_mutex);
|
||||
if (outStatus)
|
||||
*outStatus = rpcWarmupStatus;
|
||||
return fRPCInWarmup;
|
||||
}
|
||||
|
||||
bool IsDeprecatedRPCEnabled(const std::string& method)
|
||||
{
|
||||
const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc");
|
||||
|
||||
return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end();
|
||||
}
|
||||
|
||||
static UniValue JSONRPCExecOne(JSONRPCRequest jreq, const UniValue& req)
|
||||
{
|
||||
UniValue rpc_result(UniValue::VOBJ);
|
||||
|
||||
try {
|
||||
jreq.parse(req);
|
||||
|
||||
UniValue result = tableRPC.execute(jreq);
|
||||
rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id);
|
||||
}
|
||||
catch (const UniValue& objError)
|
||||
{
|
||||
rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
rpc_result = JSONRPCReplyObj(NullUniValue,
|
||||
JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
|
||||
}
|
||||
|
||||
return rpc_result;
|
||||
}
|
||||
|
||||
std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq)
|
||||
{
|
||||
UniValue ret(UniValue::VARR);
|
||||
for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++)
|
||||
ret.push_back(JSONRPCExecOne(jreq, vReq[reqIdx]));
|
||||
|
||||
return ret.write() + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Process named arguments into a vector of positional arguments, based on the
|
||||
* passed-in specification for the RPC call's arguments.
|
||||
*/
|
||||
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::string>& argNames)
|
||||
{
|
||||
JSONRPCRequest out = in;
|
||||
out.params = UniValue(UniValue::VARR);
|
||||
// Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if
|
||||
// there is an unknown one.
|
||||
const std::vector<std::string>& keys = in.params.getKeys();
|
||||
const std::vector<UniValue>& values = in.params.getValues();
|
||||
std::unordered_map<std::string, const UniValue*> argsIn;
|
||||
for (size_t i=0; i<keys.size(); ++i) {
|
||||
argsIn[keys[i]] = &values[i];
|
||||
}
|
||||
// Process expected parameters.
|
||||
int hole = 0;
|
||||
for (const std::string &argNamePattern: argNames) {
|
||||
std::vector<std::string> vargNames;
|
||||
boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|"));
|
||||
auto fr = argsIn.end();
|
||||
for (const std::string & argName : vargNames) {
|
||||
fr = argsIn.find(argName);
|
||||
if (fr != argsIn.end()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fr != argsIn.end()) {
|
||||
for (int i = 0; i < hole; ++i) {
|
||||
// Fill hole between specified parameters with JSON nulls,
|
||||
// but not at the end (for backwards compatibility with calls
|
||||
// that act based on number of specified parameters).
|
||||
out.params.push_back(UniValue());
|
||||
}
|
||||
hole = 0;
|
||||
out.params.push_back(*fr->second);
|
||||
argsIn.erase(fr);
|
||||
} else {
|
||||
hole += 1;
|
||||
}
|
||||
}
|
||||
// If there are still arguments in the argsIn map, this is an error.
|
||||
if (!argsIn.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);
|
||||
}
|
||||
// Return request with named arguments transformed to positional arguments
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool ExecuteCommands(const std::vector<const CRPCCommand*>& commands, const JSONRPCRequest& request, UniValue& result)
|
||||
{
|
||||
for (const auto& command : commands) {
|
||||
if (ExecuteCommand(*command, request, result, &command == &commands.back())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UniValue CRPCTable::execute(const JSONRPCRequest &request) const
|
||||
{
|
||||
// Return immediately if in warmup
|
||||
{
|
||||
LOCK(g_rpc_warmup_mutex);
|
||||
if (fRPCInWarmup)
|
||||
throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus);
|
||||
}
|
||||
|
||||
// Find method
|
||||
auto it = mapCommands.find(request.strMethod);
|
||||
if (it != mapCommands.end()) {
|
||||
UniValue result;
|
||||
if (ExecuteCommands(it->second, request, result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
|
||||
}
|
||||
|
||||
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler)
|
||||
{
|
||||
try
|
||||
{
|
||||
RPCCommandExecution execution(request.strMethod);
|
||||
// Execute, convert arguments to array if necessary
|
||||
if (request.params.isObject()) {
|
||||
return command.actor(transformNamedArguments(request, command.argNames), result, last_handler);
|
||||
} else {
|
||||
return command.actor(request, result, last_handler);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw JSONRPCError(RPC_MISC_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> CRPCTable::listCommands() const
|
||||
{
|
||||
std::vector<std::string> commandList;
|
||||
for (const auto& i : mapCommands) commandList.emplace_back(i.first);
|
||||
return commandList;
|
||||
}
|
||||
|
||||
UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const
|
||||
{
|
||||
JSONRPCRequest request = args_request;
|
||||
request.mode = JSONRPCRequest::GET_ARGS;
|
||||
|
||||
UniValue ret{UniValue::VARR};
|
||||
for (const auto& cmd : mapCommands) {
|
||||
UniValue result;
|
||||
if (ExecuteCommands(cmd.second, request, result)) {
|
||||
for (const auto& values : result.getValues()) {
|
||||
ret.push_back(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
|
||||
{
|
||||
if (!timerInterface)
|
||||
timerInterface = iface;
|
||||
}
|
||||
|
||||
void RPCSetTimerInterface(RPCTimerInterface *iface)
|
||||
{
|
||||
timerInterface = iface;
|
||||
}
|
||||
|
||||
void RPCUnsetTimerInterface(RPCTimerInterface *iface)
|
||||
{
|
||||
if (timerInterface == iface)
|
||||
timerInterface = nullptr;
|
||||
}
|
||||
|
||||
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds)
|
||||
{
|
||||
if (!timerInterface)
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
|
||||
LOCK(g_deadline_timers_mutex);
|
||||
deadlineTimers.erase(name);
|
||||
LogPrint(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
|
||||
deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000)));
|
||||
}
|
||||
|
||||
int RPCSerializationFlags()
|
||||
{
|
||||
int flag = 0;
|
||||
if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) == 0)
|
||||
flag |= SERIALIZE_TRANSACTION_NO_WITNESS;
|
||||
return flag;
|
||||
}
|
||||
|
||||
CRPCTable tableRPC;
|
||||
182
src/rpc/server.h
Normal file
182
src/rpc/server.h
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_SERVER_H
|
||||
#define DIGIBYTE_RPC_SERVER_H
|
||||
|
||||
#include <amount.h>
|
||||
#include <rpc/request.h>
|
||||
#include <rpc/util.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1;
|
||||
|
||||
class CRPCCommand;
|
||||
|
||||
namespace RPCServer
|
||||
{
|
||||
void OnStarted(std::function<void ()> slot);
|
||||
void OnStopped(std::function<void ()> slot);
|
||||
}
|
||||
|
||||
/** Query whether RPC is running */
|
||||
bool IsRPCRunning();
|
||||
|
||||
/** Throw JSONRPCError if RPC is not running */
|
||||
void RpcInterruptionPoint();
|
||||
|
||||
/**
|
||||
* Set the RPC warmup status. When this is done, all RPC calls will error out
|
||||
* immediately with RPC_IN_WARMUP.
|
||||
*/
|
||||
void SetRPCWarmupStatus(const std::string& newStatus);
|
||||
/* Mark warmup as done. RPC calls will be processed from now on. */
|
||||
void SetRPCWarmupFinished();
|
||||
|
||||
/* returns the current warmup state. */
|
||||
bool RPCIsInWarmup(std::string *outStatus);
|
||||
|
||||
/** Opaque base class for timers returned by NewTimerFunc.
|
||||
* This provides no methods at the moment, but makes sure that delete
|
||||
* cleans up the whole state.
|
||||
*/
|
||||
class RPCTimerBase
|
||||
{
|
||||
public:
|
||||
virtual ~RPCTimerBase() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* RPC timer "driver".
|
||||
*/
|
||||
class RPCTimerInterface
|
||||
{
|
||||
public:
|
||||
virtual ~RPCTimerInterface() {}
|
||||
/** Implementation name */
|
||||
virtual const char *Name() = 0;
|
||||
/** Factory function for timers.
|
||||
* RPC will call the function to create a timer that will call func in *millis* milliseconds.
|
||||
* @note As the RPC mechanism is backend-neutral, it can use different implementations of timers.
|
||||
* This is needed to cope with the case in which there is no HTTP server, but
|
||||
* only GUI RPC console, and to break the dependency of pcserver on httprpc.
|
||||
*/
|
||||
virtual RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) = 0;
|
||||
};
|
||||
|
||||
/** Set the factory function for timers */
|
||||
void RPCSetTimerInterface(RPCTimerInterface *iface);
|
||||
/** Set the factory function for timer, but only, if unset */
|
||||
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface);
|
||||
/** Unset factory function for timers */
|
||||
void RPCUnsetTimerInterface(RPCTimerInterface *iface);
|
||||
|
||||
/**
|
||||
* Run func nSeconds from now.
|
||||
* Overrides previous timer <name> (if any).
|
||||
*/
|
||||
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds);
|
||||
|
||||
typedef RPCHelpMan (*RpcMethodFnType)();
|
||||
|
||||
class CRPCCommand
|
||||
{
|
||||
public:
|
||||
//! RPC method handler reading request and assigning result. Should return
|
||||
//! true if request is fully handled, false if it should be passed on to
|
||||
//! subsequent handlers.
|
||||
using Actor = std::function<bool(const JSONRPCRequest& request, UniValue& result, bool last_handler)>;
|
||||
|
||||
//! Constructor taking Actor callback supporting multiple handlers.
|
||||
CRPCCommand(std::string category, std::string name, Actor actor, std::vector<std::string> args, intptr_t unique_id)
|
||||
: category(std::move(category)), name(std::move(name)), actor(std::move(actor)), argNames(std::move(args)),
|
||||
unique_id(unique_id)
|
||||
{
|
||||
}
|
||||
|
||||
//! Simplified constructor taking plain RpcMethodFnType function pointer.
|
||||
CRPCCommand(std::string category, RpcMethodFnType fn)
|
||||
: CRPCCommand(
|
||||
category,
|
||||
fn().m_name,
|
||||
[fn](const JSONRPCRequest& request, UniValue& result, bool) { result = fn().HandleRequest(request); return true; },
|
||||
fn().GetArgNames(),
|
||||
intptr_t(fn))
|
||||
{
|
||||
}
|
||||
|
||||
std::string category;
|
||||
std::string name;
|
||||
Actor actor;
|
||||
std::vector<std::string> argNames;
|
||||
intptr_t unique_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* RPC command dispatcher.
|
||||
*/
|
||||
class CRPCTable
|
||||
{
|
||||
private:
|
||||
std::map<std::string, std::vector<const CRPCCommand*>> mapCommands;
|
||||
public:
|
||||
CRPCTable();
|
||||
std::string help(const std::string& name, const JSONRPCRequest& helpreq) const;
|
||||
|
||||
/**
|
||||
* Execute a method.
|
||||
* @param request The JSONRPCRequest to execute
|
||||
* @returns Result of the call.
|
||||
* @throws an exception (UniValue) when an error happens.
|
||||
*/
|
||||
UniValue execute(const JSONRPCRequest &request) const;
|
||||
|
||||
/**
|
||||
* Returns a list of registered commands
|
||||
* @returns List of registered commands.
|
||||
*/
|
||||
std::vector<std::string> listCommands() const;
|
||||
|
||||
/**
|
||||
* Return all named arguments that need to be converted by the client from string to another JSON type
|
||||
*/
|
||||
UniValue dumpArgMap(const JSONRPCRequest& request) const;
|
||||
|
||||
/**
|
||||
* Appends a CRPCCommand to the dispatch table.
|
||||
*
|
||||
* Precondition: RPC server is not running
|
||||
*
|
||||
* Commands with different method names but the same unique_id will
|
||||
* be considered aliases, and only the first registered method name will
|
||||
* show up in the help text command listing. Aliased commands do not have
|
||||
* to have the same behavior. Server and client code can distinguish
|
||||
* between calls based on method name, and aliased commands can also
|
||||
* register different names, types, and numbers of parameters.
|
||||
*/
|
||||
void appendCommand(const std::string& name, const CRPCCommand* pcmd);
|
||||
bool removeCommand(const std::string& name, const CRPCCommand* pcmd);
|
||||
};
|
||||
|
||||
bool IsDeprecatedRPCEnabled(const std::string& method);
|
||||
|
||||
extern CRPCTable tableRPC;
|
||||
|
||||
void StartRPC();
|
||||
void InterruptRPC();
|
||||
void StopRPC();
|
||||
std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq);
|
||||
|
||||
// Retrieves any serialization flags requested in command line argument
|
||||
int RPCSerializationFlags();
|
||||
|
||||
#endif // DIGIBYTE_RPC_SERVER_H
|
||||
1039
src/rpc/util.cpp
Normal file
1039
src/rpc/util.cpp
Normal file
File diff suppressed because it is too large
Load diff
371
src/rpc/util.h
Normal file
371
src/rpc/util.h
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
// Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2020 The DigiByte Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef DIGIBYTE_RPC_UTIL_H
|
||||
#define DIGIBYTE_RPC_UTIL_H
|
||||
|
||||
#include <node/coinstats.h>
|
||||
#include <node/transaction.h>
|
||||
#include <outputtype.h>
|
||||
#include <protocol.h>
|
||||
#include <pubkey.h>
|
||||
#include <rpc/protocol.h>
|
||||
#include <rpc/request.h>
|
||||
#include <script/script.h>
|
||||
#include <script/sign.h>
|
||||
#include <script/standard.h>
|
||||
#include <univalue.h>
|
||||
#include <util/check.h>
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* String used to describe UNIX epoch time in documentation, factored out to a
|
||||
* constant for consistency.
|
||||
*/
|
||||
extern const std::string UNIX_EPOCH_TIME;
|
||||
|
||||
/**
|
||||
* Example bech32 addresses for the RPCExamples help documentation. They are intentionally
|
||||
* invalid to prevent accidental transactions by users.
|
||||
*/
|
||||
extern const std::string EXAMPLE_ADDRESS[2];
|
||||
|
||||
class FillableSigningProvider;
|
||||
class CPubKey;
|
||||
class CScript;
|
||||
struct Sections;
|
||||
|
||||
/** Wrapper for UniValue::VType, which includes typeAny:
|
||||
* Used to denote don't care type. */
|
||||
struct UniValueType {
|
||||
UniValueType(UniValue::VType _type) : typeAny(false), type(_type) {}
|
||||
UniValueType() : typeAny(true) {}
|
||||
bool typeAny;
|
||||
UniValue::VType type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type-check arguments; throws JSONRPCError if wrong type given. Does not check that
|
||||
* the right number of arguments are passed, just that any passed are the correct type.
|
||||
*/
|
||||
void RPCTypeCheck(const UniValue& params,
|
||||
const std::list<UniValueType>& typesExpected, bool fAllowNull=false);
|
||||
|
||||
/**
|
||||
* Type-check one argument; throws JSONRPCError if wrong type given.
|
||||
*/
|
||||
void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected);
|
||||
|
||||
/*
|
||||
Check for expected keys/value types in an Object.
|
||||
*/
|
||||
void RPCTypeCheckObj(const UniValue& o,
|
||||
const std::map<std::string, UniValueType>& typesExpected,
|
||||
bool fAllowNull = false,
|
||||
bool fStrict = false);
|
||||
|
||||
/**
|
||||
* Utilities: convert hex-encoded Values
|
||||
* (throws error if not hex).
|
||||
*/
|
||||
uint256 ParseHashV(const UniValue& v, std::string strName);
|
||||
uint256 ParseHashO(const UniValue& o, std::string strKey);
|
||||
std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName);
|
||||
std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
|
||||
|
||||
/**
|
||||
* Validate and return a CAmount from a UniValue number or string.
|
||||
*
|
||||
* @param[in] value UniValue number or string to parse.
|
||||
* @param[in] decimals Number of significant digits (default: 8).
|
||||
* @returns a CAmount if the various checks pass.
|
||||
*/
|
||||
CAmount AmountFromValue(const UniValue& value, int decimals = 8);
|
||||
|
||||
using RPCArgList = std::vector<std::pair<std::string, UniValue>>;
|
||||
std::string HelpExampleCli(const std::string& methodname, const std::string& args);
|
||||
std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args);
|
||||
std::string HelpExampleRpc(const std::string& methodname, const std::string& args);
|
||||
std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args);
|
||||
|
||||
CPubKey HexToPubKey(const std::string& hex_in);
|
||||
CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in);
|
||||
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out);
|
||||
|
||||
UniValue DescribeAddress(const CTxDestination& dest);
|
||||
|
||||
//! Parse a confirm target option and raise an RPC error if it is invalid.
|
||||
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);
|
||||
|
||||
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
|
||||
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
|
||||
|
||||
//! Parse a JSON range specified as int64, or [int64, int64]
|
||||
std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value);
|
||||
|
||||
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
|
||||
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider);
|
||||
|
||||
/** Returns, given services flags, a list of humanly readable (known) network services */
|
||||
UniValue GetServicesNames(ServiceFlags services);
|
||||
|
||||
/**
|
||||
* Serializing JSON objects depends on the outer type. Only arrays and
|
||||
* dictionaries can be nested in json. The top-level outer type is "NONE".
|
||||
*/
|
||||
enum class OuterType {
|
||||
ARR,
|
||||
OBJ,
|
||||
NONE, // Only set on first recursion
|
||||
};
|
||||
|
||||
struct RPCArg {
|
||||
enum class Type {
|
||||
OBJ,
|
||||
ARR,
|
||||
STR,
|
||||
NUM,
|
||||
BOOL,
|
||||
OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined
|
||||
AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR)
|
||||
STR_HEX, //!< Special type that is a STR with only hex chars
|
||||
RANGE, //!< Special type that is a NUM or [NUM,NUM]
|
||||
};
|
||||
|
||||
enum class Optional {
|
||||
/** Required arg */
|
||||
NO,
|
||||
/**
|
||||
* Optional arg that is a named argument and has a default value of
|
||||
* `null`. When possible, the default value should be specified.
|
||||
*/
|
||||
OMITTED_NAMED_ARG,
|
||||
/**
|
||||
* Optional argument with default value omitted because they are
|
||||
* implicitly clear. That is, elements in an array or object may not
|
||||
* exist by default.
|
||||
* When possible, the default value should be specified.
|
||||
*/
|
||||
OMITTED,
|
||||
};
|
||||
using DefaultHint = std::string;
|
||||
using Default = UniValue;
|
||||
using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>;
|
||||
const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments)
|
||||
const Type m_type;
|
||||
const bool m_hidden;
|
||||
const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts
|
||||
const Fallback m_fallback;
|
||||
const std::string m_description;
|
||||
const std::string m_oneline_description; //!< Should be empty unless it is supposed to override the auto-generated summary line
|
||||
const std::vector<std::string> m_type_str; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_type_str.at(0) will override the type of the value in a key-value pair, m_type_str.at(1) will override the type in the argument description.
|
||||
|
||||
RPCArg(
|
||||
const std::string name,
|
||||
const Type type,
|
||||
const Fallback fallback,
|
||||
const std::string description,
|
||||
const std::string oneline_description = "",
|
||||
const std::vector<std::string> type_str = {},
|
||||
const bool hidden = false)
|
||||
: m_names{std::move(name)},
|
||||
m_type{std::move(type)},
|
||||
m_hidden{hidden},
|
||||
m_fallback{std::move(fallback)},
|
||||
m_description{std::move(description)},
|
||||
m_oneline_description{std::move(oneline_description)},
|
||||
m_type_str{std::move(type_str)}
|
||||
{
|
||||
CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_USER_KEYS);
|
||||
}
|
||||
|
||||
RPCArg(
|
||||
const std::string name,
|
||||
const Type type,
|
||||
const Fallback fallback,
|
||||
const std::string description,
|
||||
const std::vector<RPCArg> inner,
|
||||
const std::string oneline_description = "",
|
||||
const std::vector<std::string> type_str = {})
|
||||
: m_names{std::move(name)},
|
||||
m_type{std::move(type)},
|
||||
m_hidden{false},
|
||||
m_inner{std::move(inner)},
|
||||
m_fallback{std::move(fallback)},
|
||||
m_description{std::move(description)},
|
||||
m_oneline_description{std::move(oneline_description)},
|
||||
m_type_str{std::move(type_str)}
|
||||
{
|
||||
CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_USER_KEYS);
|
||||
}
|
||||
|
||||
bool IsOptional() const;
|
||||
|
||||
/** Return the first of all aliases */
|
||||
std::string GetFirstName() const;
|
||||
|
||||
/** Return the name, throws when there are aliases */
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
* Return the type string of the argument.
|
||||
* Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description).
|
||||
*/
|
||||
std::string ToString(bool oneline) const;
|
||||
/**
|
||||
* Return the type string of the argument when it is in an object (dict).
|
||||
* Set oneline to get the oneline representation (less whitespace)
|
||||
*/
|
||||
std::string ToStringObj(bool oneline) const;
|
||||
/**
|
||||
* Return the description string, including the argument type and whether
|
||||
* the argument is required.
|
||||
*/
|
||||
std::string ToDescriptionString() const;
|
||||
};
|
||||
|
||||
struct RPCResult {
|
||||
enum class Type {
|
||||
OBJ,
|
||||
ARR,
|
||||
STR,
|
||||
NUM,
|
||||
BOOL,
|
||||
NONE,
|
||||
ANY, //!< Special type to disable type checks (for testing only)
|
||||
STR_AMOUNT, //!< Special string to represent a floating point amount
|
||||
STR_HEX, //!< Special string with only hex chars
|
||||
OBJ_DYN, //!< Special dictionary with keys that are not literals
|
||||
ARR_FIXED, //!< Special array that has a fixed number of entries
|
||||
NUM_TIME, //!< Special numeric to denote unix epoch time
|
||||
ELISION, //!< Special type to denote elision (...)
|
||||
};
|
||||
|
||||
const Type m_type;
|
||||
const std::string m_key_name; //!< Only used for dicts
|
||||
const std::vector<RPCResult> m_inner; //!< Only used for arrays or dicts
|
||||
const bool m_optional;
|
||||
const std::string m_description;
|
||||
const std::string m_cond;
|
||||
|
||||
RPCResult(
|
||||
const std::string cond,
|
||||
const Type type,
|
||||
const std::string m_key_name,
|
||||
const bool optional,
|
||||
const std::string description,
|
||||
const std::vector<RPCResult> inner = {})
|
||||
: m_type{std::move(type)},
|
||||
m_key_name{std::move(m_key_name)},
|
||||
m_inner{std::move(inner)},
|
||||
m_optional{optional},
|
||||
m_description{std::move(description)},
|
||||
m_cond{std::move(cond)}
|
||||
{
|
||||
CHECK_NONFATAL(!m_cond.empty());
|
||||
const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
|
||||
CHECK_NONFATAL(inner_needed != inner.empty());
|
||||
}
|
||||
|
||||
RPCResult(
|
||||
const std::string cond,
|
||||
const Type type,
|
||||
const std::string m_key_name,
|
||||
const std::string description,
|
||||
const std::vector<RPCResult> inner = {})
|
||||
: RPCResult{cond, type, m_key_name, false, description, inner} {}
|
||||
|
||||
RPCResult(
|
||||
const Type type,
|
||||
const std::string m_key_name,
|
||||
const bool optional,
|
||||
const std::string description,
|
||||
const std::vector<RPCResult> inner = {})
|
||||
: m_type{std::move(type)},
|
||||
m_key_name{std::move(m_key_name)},
|
||||
m_inner{std::move(inner)},
|
||||
m_optional{optional},
|
||||
m_description{std::move(description)},
|
||||
m_cond{}
|
||||
{
|
||||
const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
|
||||
CHECK_NONFATAL(inner_needed != inner.empty());
|
||||
}
|
||||
|
||||
RPCResult(
|
||||
const Type type,
|
||||
const std::string m_key_name,
|
||||
const std::string description,
|
||||
const std::vector<RPCResult> inner = {})
|
||||
: RPCResult{type, m_key_name, false, description, inner} {}
|
||||
|
||||
/** Append the sections of the result. */
|
||||
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const;
|
||||
/** Return the type string of the result when it is in an object (dict). */
|
||||
std::string ToStringObj() const;
|
||||
/** Return the description string, including the result type. */
|
||||
std::string ToDescriptionString() const;
|
||||
/** Check whether the result JSON type matches. */
|
||||
bool MatchesType(const UniValue& result) const;
|
||||
};
|
||||
|
||||
struct RPCResults {
|
||||
const std::vector<RPCResult> m_results;
|
||||
|
||||
RPCResults(RPCResult result)
|
||||
: m_results{{result}}
|
||||
{
|
||||
}
|
||||
|
||||
RPCResults(std::initializer_list<RPCResult> results)
|
||||
: m_results{results}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description string.
|
||||
*/
|
||||
std::string ToDescriptionString() const;
|
||||
};
|
||||
|
||||
struct RPCExamples {
|
||||
const std::string m_examples;
|
||||
explicit RPCExamples(
|
||||
std::string examples)
|
||||
: m_examples(std::move(examples))
|
||||
{
|
||||
}
|
||||
std::string ToDescriptionString() const;
|
||||
};
|
||||
|
||||
class RPCHelpMan
|
||||
{
|
||||
public:
|
||||
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples);
|
||||
using RPCMethodImpl = std::function<UniValue(const RPCHelpMan&, const JSONRPCRequest&)>;
|
||||
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun);
|
||||
|
||||
UniValue HandleRequest(const JSONRPCRequest& request) const;
|
||||
std::string ToString() const;
|
||||
/** Return the named args that need to be converted from string to another JSON type */
|
||||
UniValue GetArgMap() const;
|
||||
/** If the supplied number of args is neither too small nor too high */
|
||||
bool IsValidNumArgs(size_t num_args) const;
|
||||
std::vector<std::string> GetArgNames() const;
|
||||
|
||||
const std::string m_name;
|
||||
|
||||
private:
|
||||
const RPCMethodImpl m_fun;
|
||||
const std::string m_description;
|
||||
const std::vector<RPCArg> m_args;
|
||||
const RPCResults m_results;
|
||||
const RPCExamples m_examples;
|
||||
};
|
||||
|
||||
#endif // DIGIBYTE_RPC_UTIL_H
|
||||
Loading…
Reference in a new issue