diff --git a/doc/release-notes.md b/doc/release-notes.md
index d9565d95e..c4dd895e4 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,8 +1,13 @@
Bitcoin ABC version 0.20.13 is now available from:
This release includes the following features and fixes:
- The RPC `getrpcinfo` returns runtime details of the RPC server. At the moment
it returns the active commands and the corresponding execution time.
+
+
+New RPC methods
+------------
+- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds.
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 39167fcfa..f691eb846 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -1,251 +1,252 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
#include
#include
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
};
/**
* 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"},
{"generate", 0, "nblocks"},
{"generate", 1, "maxtries"},
{"generatetoaddress", 0, "nblocks"},
{"generatetoaddress", 2, "maxtries"},
{"getnetworkhashps", 0, "nblocks"},
{"getnetworkhashps", 1, "height"},
{"sendtoaddress", 1, "amount"},
{"sendtoaddress", 4, "subtractfeefromamount"},
{"settxfee", 0, "amount"},
{"sethdseed", 0, "newkeypool"},
{"getreceivedbyaddress", 1, "minconf"},
{"getreceivedbyaccount", 1, "minconf"},
{"getreceivedbylabel", 1, "minconf"},
{"listreceivedbyaddress", 0, "minconf"},
{"listreceivedbyaddress", 1, "include_empty"},
{"listreceivedbyaddress", 2, "include_watchonly"},
{"listreceivedbyaddress", 3, "address_filter"},
{"listreceivedbyaccount", 0, "minconf"},
{"listreceivedbyaccount", 1, "include_empty"},
{"listreceivedbyaccount", 2, "include_watchonly"},
{"listreceivedbylabel", 0, "minconf"},
{"listreceivedbylabel", 1, "include_empty"},
{"listreceivedbylabel", 2, "include_watchonly"},
{"getbalance", 1, "minconf"},
{"getbalance", 2, "include_watchonly"},
{"getblockhash", 0, "height"},
{"waitforblockheight", 0, "height"},
{"waitforblockheight", 1, "timeout"},
{"waitforblock", 1, "timeout"},
{"waitfornewblock", 0, "timeout"},
{"move", 2, "amount"},
{"move", 3, "minconf"},
{"sendfrom", 2, "amount"},
{"sendfrom", 3, "minconf"},
{"listtransactions", 1, "count"},
{"listtransactions", 2, "skip"},
{"listtransactions", 3, "include_watchonly"},
{"listaccounts", 0, "minconf"},
{"listaccounts", 1, "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"},
{"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"},
{"getrawtransaction", 1, "verbose"},
{"createrawtransaction", 0, "inputs"},
{"createrawtransaction", 1, "outputs"},
{"createrawtransaction", 2, "locktime"},
{"signrawtransactionwithkey", 1, "privkeys"},
{"signrawtransactionwithkey", 2, "prevtxs"},
{"signrawtransactionwithwallet", 1, "prevtxs"},
{"sendrawtransaction", 1, "allowhighfees"},
{"testmempoolaccept", 0, "rawtxs"},
{"testmempoolaccept", 1, "allowhighfees"},
{"combinerawtransaction", 0, "txs"},
{"fundrawtransaction", 1, "options"},
{"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"},
{"combinepsbt", 0, "txs"},
{"finalizepsbt", 1, "extract"},
{"converttopsbt", 1, "permitsigdata"},
{"gettxout", 1, "n"},
{"gettxout", 2, "include_mempool"},
{"gettxoutproof", 0, "txids"},
{"lockunspent", 0, "unlock"},
{"lockunspent", 1, "transactions"},
{"importprivkey", 2, "rescan"},
{"importaddress", 2, "rescan"},
{"importaddress", 3, "p2sh"},
{"importpubkey", 2, "rescan"},
{"importmulti", 0, "requests"},
{"importmulti", 1, "options"},
{"verifychain", 0, "checklevel"},
{"verifychain", 1, "nblocks"},
{"getblockstats", 0, "hash_or_height"},
{"getblockstats", 1, "stats"},
{"pruneblockchain", 0, "height"},
{"keypoolrefill", 0, "newsize"},
{"getrawmempool", 0, "verbose"},
{"estimatefee", 0, "nblocks"},
{"prioritisetransaction", 1, "dummy"},
{"prioritisetransaction", 2, "fee_delta"},
{"setban", 2, "bantime"},
{"setban", 3, "absolute"},
{"setnetworkactive", 0, "state"},
{"getmempoolancestors", 1, "verbose"},
{"getmempooldescendants", 1, "verbose"},
{"disconnectnode", 1, "nodeid"},
{"logging", 0, "include"},
{"logging", 1, "exclude"},
// 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"},
+ {"getnodeaddresses", 0, "count"},
{"stop", 0, "wait"},
};
class CRPCConvertTable {
private:
std::set> members;
std::set> 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() {
const unsigned int n_elem =
(sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0]));
for (unsigned int i = 0; i < n_elem; i++) {
members.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].paramIdx));
membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].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 &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 &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;
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 0c3090ed0..8a5c615b4 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,811 +1,873 @@
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static UniValue getconnectioncount(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"getconnectioncount\n"
"\nReturns the number of connections to other nodes.\n"
"\nResult:\n"
"n (numeric) The connection count\n"
"\nExamples:\n" +
HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", ""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
return int(g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL));
}
static UniValue ping(const Config &config, const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"ping\n"
"\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"
"\nExamples:\n" +
HelpExampleCli("ping", "") + HelpExampleRpc("ping", ""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
// Request that each node send a ping during next message processing pass
g_connman->ForEachNode([](CNode *pnode) { pnode->fPingQueued = true; });
return NullUniValue;
}
static UniValue getpeerinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"getpeerinfo\n"
"\nReturns data about each connected network node as a json array "
"of objects.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"id\": n, (numeric) Peer index\n"
" \"addr\":\"host:port\", (string) The IP address and port "
"of the peer\n"
" \"addrbind\":\"ip:port\", (string) Bind address of the "
"connection to the peer\n"
" \"addrlocal\":\"ip:port\", (string) Local address as "
"reported by the peer\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services "
"offered\n"
" \"relaytxes\":true|false, (boolean) Whether peer has asked "
"us to relay transactions to it\n"
" \"lastsend\": ttt, (numeric) The time in seconds "
"since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds "
"since epoch (Jan 1 1970 GMT) of the last receive\n"
" \"bytessent\": n, (numeric) The total bytes sent\n"
" \"bytesrecv\": n, (numeric) The total bytes "
"received\n"
" \"conntime\": ttt, (numeric) The connection time in "
"seconds since epoch (Jan 1 1970 GMT)\n"
" \"timeoffset\": ttt, (numeric) The time offset in "
"seconds\n"
" \"pingtime\": n, (numeric) ping time (if "
"available)\n"
" \"minping\": n, (numeric) minimum observed ping "
"time (if any at all)\n"
" \"pingwait\": n, (numeric) ping wait (if "
"non-zero)\n"
" \"version\": v, (numeric) The peer version, such "
"as 70001\n"
" \"subver\": \"/Satoshi:0.8.5/\", (string) The string "
"version\n"
" \"inbound\": true|false, (boolean) Inbound (true) or "
"Outbound (false)\n"
" \"addnode\": true|false, (boolean) Whether connection was "
"due to addnode/-connect or if it was an automatic/inbound "
"connection\n"
" \"startingheight\": n, (numeric) The starting height "
"(block) of the peer\n"
" \"banscore\": n, (numeric) The ban score\n"
" \"synced_headers\": n, (numeric) The last header we "
"have in common with this peer\n"
" \"synced_blocks\": n, (numeric) The last block we have "
"in common with this peer\n"
" \"inflight\": [\n"
" n, (numeric) The heights of blocks "
"we're currently asking from this peer\n"
" ...\n"
" ],\n"
" \"whitelisted\": true|false, (boolean) Whether the peer is "
"whitelisted\n"
" \"minfeefilter\": n, (numeric) The minimum fee rate "
"for transactions this peer accepts\n"
" \"bytessent_per_msg\": {\n"
" \"addr\": n, (numeric) The total bytes sent "
"aggregated by message type\n"
" ...\n"
" },\n"
" \"bytesrecv_per_msg\": {\n"
" \"addr\": n, (numeric) The total bytes "
"received aggregated by message type\n"
" ...\n"
" }\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", ""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector vstats;
g_connman->GetNodeStats(vstats);
UniValue ret(UniValue::VARR);
for (const CNodeStats &stats : vstats) {
UniValue obj(UniValue::VOBJ);
CNodeStateStats statestats;
bool fStateStats = GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.addrName);
if (!(stats.addrLocal.empty())) {
obj.pushKV("addrlocal", stats.addrLocal);
}
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", stats.nLastSend);
obj.pushKV("lastrecv", stats.nLastRecv);
obj.pushKV("bytessent", stats.nSendBytes);
obj.pushKV("bytesrecv", stats.nRecvBytes);
obj.pushKV("conntime", stats.nTimeConnected);
obj.pushKV("timeoffset", stats.nTimeOffset);
if (stats.dPingTime > 0.0) {
obj.pushKV("pingtime", stats.dPingTime);
}
if (stats.dMinPing <
static_cast(std::numeric_limits::max()) / 1e6) {
obj.pushKV("minping", stats.dMinPing);
}
if (stats.dPingWait > 0.0) {
obj.pushKV("pingwait", stats.dPingWait);
}
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("addnode", stats.m_manual_connection);
obj.pushKV("startingheight", stats.nStartingHeight);
if (fStateStats) {
obj.pushKV("banscore", statestats.nMisbehavior);
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("whitelisted", stats.fWhitelisted);
obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const mapMsgCmdSize::value_type &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 mapMsgCmdSize::value_type &i : stats.mapRecvBytesPerMsgCmd) {
if (i.second > 0) {
recvPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
ret.push_back(obj);
}
return ret;
}
static UniValue addnode(const Config &config, const JSONRPCRequest &request) {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || request.params.size() != 2 ||
(strCommand != "onetry" && strCommand != "add" &&
strCommand != "remove")) {
throw std::runtime_error(
"addnode \"node\" \"add|remove|onetry\"\n"
"\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 as other outbound peers are (though "
"such peers will not be synced from).\n"
"\nArguments:\n"
"1. \"node\" (string, required) The node (see getpeerinfo for "
"nodes)\n"
"2. \"command\" (string, required) '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\n"
"\nExamples:\n" +
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::string strNode = request.params[0].get_str();
if (strCommand == "onetry") {
CAddress addr;
g_connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str(),
false, false, true);
return NullUniValue;
}
if ((strCommand == "add") && (!g_connman->AddNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if ((strCommand == "remove") &&
(!g_connman->RemoveAddedNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
return NullUniValue;
}
static UniValue disconnectnode(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() == 0 ||
request.params.size() >= 3) {
throw std::runtime_error(
"disconnectnode \"[address]\" [nodeid]\n"
"\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"
"\nArguments:\n"
"1. \"address\" (string, optional) The IP address/port of the "
"node\n"
"2. \"nodeid\" (number, optional) The node ID (see "
"getpeerinfo for node IDs)\n"
"\nExamples:\n" +
HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1"));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
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 = g_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 = g_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 UniValue getaddednodeinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() > 1) {
throw std::runtime_error(
"getaddednodeinfo ( \"node\" )\n"
"\nReturns information about the given added node, or all added "
"nodes\n"
"(note that onetry addnodes are not listed here)\n"
"\nArguments:\n"
"1. \"node\" (string, optional) If provided, return information "
"about this specific node, otherwise all nodes are returned.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"addednode\" : \"192.168.0.201\", (string) The node IP "
"address or name (as provided to addnode)\n"
" \"connected\" : true|false, (boolean) If connected\n"
" \"addresses\" : [ (list of objects) Only "
"when connected = true\n"
" {\n"
" \"address\" : \"192.168.0.201:8333\", (string) The "
"bitcoin server IP and port we're connected to\n"
" \"connected\" : \"outbound\" (string) "
"connection, inbound or outbound\n"
" }\n"
" ]\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") +
HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector vInfo = g_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 UniValue getnettotals(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() > 0) {
throw std::runtime_error(
"getnettotals\n"
"\nReturns information about network traffic, including bytes in, "
"bytes out,\n"
"and current time.\n"
"\nResult:\n"
"{\n"
" \"totalbytesrecv\": n, (numeric) Total bytes received\n"
" \"totalbytessent\": n, (numeric) Total bytes sent\n"
" \"timemillis\": t, (numeric) Current UNIX time in "
"milliseconds\n"
" \"uploadtarget\":\n"
" {\n"
" \"timeframe\": n, (numeric) Length of "
"the measuring timeframe in seconds\n"
" \"target\": n, (numeric) Target in "
"bytes\n"
" \"target_reached\": true|false, (boolean) True if "
"target is reached\n"
" \"serve_historical_blocks\": true|false, (boolean) True if "
"serving historical blocks\n"
" \"bytes_left_in_cycle\": t, (numeric) Bytes "
"left in current time cycle\n"
" \"time_left_in_cycle\": t (numeric) Seconds "
"left in current time cycle\n"
" }\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getnettotals", "") +
HelpExampleRpc("getnettotals", ""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("totalbytesrecv", g_connman->GetTotalBytesRecv());
obj.pushKV("totalbytessent", g_connman->GetTotalBytesSent());
obj.pushKV("timemillis", GetTimeMillis());
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.pushKV("timeframe", g_connman->GetMaxOutboundTimeframe());
outboundLimit.pushKV("target", g_connman->GetMaxOutboundTarget());
outboundLimit.pushKV("target_reached",
g_connman->OutboundTargetReached(false));
outboundLimit.pushKV("serve_historical_blocks",
!g_connman->OutboundTargetReached(true));
outboundLimit.pushKV("bytes_left_in_cycle",
g_connman->GetOutboundTargetBytesLeft());
outboundLimit.pushKV("time_left_in_cycle",
g_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(n);
if (network == NET_UNROUTABLE || 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 UniValue getnetworkinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"getnetworkinfo\n"
"Returns an object containing various state info regarding P2P "
"networking.\n"
"\nResult:\n"
"{\n"
" \"version\": xxxxx, (numeric) the server "
"version\n"
" \"subversion\": \"/Satoshi:x.x.x/\", (string) the server "
"subversion string\n"
" \"protocolversion\": xxxxx, (numeric) the protocol "
"version\n"
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services "
"we offer to the network\n"
" \"localrelay\": true|false, (bool) true if "
"transaction relay is requested from peers\n"
" \"timeoffset\": xxxxx, (numeric) the time "
"offset\n"
" \"connections\": xxxxx, (numeric) the number "
"of connections\n"
" \"networkactive\": true|false, (bool) whether p2p "
"networking is enabled\n"
" \"networks\": [ (array) information "
"per network\n"
" {\n"
" \"name\": \"xxx\", (string) network "
"(ipv4, ipv6 or onion)\n"
" \"limited\": true|false, (boolean) is the "
"network limited using -onlynet?\n"
" \"reachable\": true|false, (boolean) is the "
"network reachable?\n"
" \"proxy\": \"host:port\" (string) the proxy "
"that is used for this network, or empty if none\n"
" \"proxy_randomize_credentials\": true|false, (string) "
"Whether randomized credentials are used\n"
" }\n"
" ,...\n"
" ],\n"
" \"relayfee\": x.xxxxxxxx, (numeric) minimum "
"relay fee for transactions in " +
CURRENCY_UNIT +
"/kB\n"
" \"excessutxocharge\": x.xxxxxxxx, (numeric) minimum "
"charge for excess utxos in " +
CURRENCY_UNIT +
"\n"
" \"localaddresses\": [ "
"(array) list of local addresses\n"
" {\n"
" \"address\": \"xxxx\", "
"(string) network address\n"
" \"port\": xxx, "
"(numeric) network port\n"
" \"score\": xxx "
"(numeric) relative score\n"
" }\n"
" ,...\n"
" ]\n"
" \"warnings\": \"...\" (string) any network "
"and blockchain warnings\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getnetworkinfo", "") +
HelpExampleRpc("getnetworkinfo", ""));
}
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", userAgent(config));
obj.pushKV("protocolversion", PROTOCOL_VERSION);
if (g_connman) {
obj.pushKV("localservices",
strprintf("%016x", g_connman->GetLocalServices()));
}
obj.pushKV("localrelay", fRelayTxes);
obj.pushKV("timeoffset", GetTimeOffset());
if (g_connman) {
obj.pushKV("networkactive", g_connman->GetNetworkActive());
obj.pushKV("connections",
int(g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL)));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
obj.pushKV("excessutxocharge",
ValueFromAmount(config.GetExcessUTXOCharge()));
UniValue localAddresses(UniValue::VARR);
{
LOCK(cs_mapLocalHost);
for (const std::pair &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("statusbar"));
return obj;
}
static UniValue setban(const Config &config, const JSONRPCRequest &request) {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || request.params.size() < 2 ||
(strCommand != "add" && strCommand != "remove")) {
throw std::runtime_error(
"setban \"subnet\" \"add|remove\" (bantime) (absolute)\n"
"\nAttempts to add or remove a IP/Subnet from the banned list.\n"
"\nArguments:\n"
"1. \"subnet\" (string, required) The IP/Subnet (see "
"getpeerinfo for nodes IP) with an optional netmask (default is "
"/32 "
"= single IP)\n"
"2. \"command\" (string, required) 'add' to add an IP/Subnet "
"to the list, 'remove' to remove an IP/Subnet from the list\n"
"3. \"bantime\" (numeric, optional) 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)\n"
"4. \"absolute\" (boolean, optional) If set, the bantime must "
"be an absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n"
"\nExamples:\n" +
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400"));
}
if (!g_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().c_str(), resolved, false);
netAddr = resolved;
} else {
LookupSubNet(request.params[0].get_str().c_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 ? g_banman->IsBanned(subNet)
: g_banman->IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: IP/Subnet already banned");
}
// Use standard bantime if not specified.
int64_t banTime = 0;
if (!request.params[2].isNull()) {
banTime = request.params[2].get_int64();
}
bool absolute = false;
if (request.params[3].isTrue()) {
absolute = true;
}
if (isSubnet) {
g_banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute);
if (g_connman) {
g_connman->DisconnectNode(subNet);
}
} else {
g_banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute);
if (g_connman) {
g_connman->DisconnectNode(netAddr);
}
}
} else if (strCommand == "remove") {
if (!(isSubnet ? g_banman->Unban(subNet) : g_banman->Unban(netAddr))) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Unban failed. Requested address/subnet "
"was not previously banned.");
}
}
return NullUniValue;
}
static UniValue listbanned(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error("listbanned\n"
"\nList all banned IPs/Subnets.\n"
"\nExamples:\n" +
HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", ""));
}
if (!g_banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
banmap_t banMap;
g_banman->GetBanned(banMap);
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("banned_until", banEntry.nBanUntil);
rec.pushKV("ban_created", banEntry.nCreateTime);
rec.pushKV("ban_reason", banEntry.banReasonToString());
bannedAddresses.push_back(rec);
}
return bannedAddresses;
}
static UniValue clearbanned(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error("clearbanned\n"
"\nClear all banned IPs.\n"
"\nExamples:\n" +
HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", ""));
}
if (!g_banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
g_banman->ClearBanned();
return NullUniValue;
}
static UniValue setnetworkactive(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"setnetworkactive true|false\n"
"\nDisable/enable all p2p network activity.\n"
"\nArguments:\n"
"1. \"state\" (boolean, required) true to "
"enable networking, false to disable\n");
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
g_connman->SetNetworkActive(request.params[0].get_bool());
return g_connman->GetNetworkActive();
}
+static UniValue getnodeaddresses(const Config &config,
+ const JSONRPCRequest &request) {
+ if (request.fHelp || request.params.size() > 1) {
+ throw std::runtime_error(
+ "getnodeaddresses ( count )\n"
+ "\nReturn known addresses which can potentially be used to find "
+ "new nodes in the network\n"
+ "\nArguments:\n"
+ "1. \"count\" (numeric, optional) How many addresses to return. "
+ "Limited to the smaller of " +
+ std::to_string(ADDRMAN_GETADDR_MAX) + " or " +
+ std::to_string(ADDRMAN_GETADDR_MAX_PCT) +
+ "% of all known addresses. (default = 1)\n"
+ "\nResult:\n"
+ "[\n"
+ " {\n"
+ " \"time\": ttt, (numeric) Timestamp in seconds "
+ "since epoch (Jan 1 1970 GMT) keeping track of when the node was "
+ "last seen\n"
+ " \"services\": n, (numeric) The services offered\n"
+ " \"address\": \"host\", (string) The address of the "
+ "node\n"
+ " \"port\": n (numeric) The port of the node\n"
+ " }\n"
+ " ,....\n"
+ "]\n"
+ "\nExamples:\n" +
+ HelpExampleCli("getnodeaddresses", "8") +
+ HelpExampleRpc("getnodeaddresses", "8"));
+ }
+ if (!g_connman) {
+ throw JSONRPCError(
+ RPC_CLIENT_P2P_DISABLED,
+ "Error: Peer-to-peer functionality missing or disabled");
+ }
+
+ int count = 1;
+ if (!request.params[0].isNull()) {
+ count = request.params[0].get_int();
+ if (count <= 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER,
+ "Address count out of range");
+ }
+ }
+ // returns a shuffled list of CAddress
+ std::vector vAddr = g_connman->GetAddresses();
+ UniValue ret(UniValue::VARR);
+
+ int address_return_count = std::min(count, vAddr.size());
+ for (int i = 0; i < address_return_count; ++i) {
+ UniValue obj(UniValue::VOBJ);
+ const CAddress &addr = vAddr[i];
+ obj.pushKV("time", int(addr.nTime));
+ obj.pushKV("services", uint64_t(addr.nServices));
+ obj.pushKV("address", addr.ToStringIP());
+ obj.pushKV("port", addr.GetPort());
+ ret.push_back(obj);
+ }
+ return ret;
+}
+
// clang-format off
static const ContextFreeRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "network", "getconnectioncount", getconnectioncount, {} },
{ "network", "ping", ping, {} },
{ "network", "getpeerinfo", getpeerinfo, {} },
{ "network", "addnode", addnode, {"node","command"} },
{ "network", "disconnectnode", disconnectnode, {"address", "nodeid"} },
{ "network", "getaddednodeinfo", getaddednodeinfo, {"node"} },
{ "network", "getnettotals", getnettotals, {} },
{ "network", "getnetworkinfo", getnetworkinfo, {} },
{ "network", "setban", setban, {"subnet", "command", "bantime", "absolute"} },
{ "network", "listbanned", listbanned, {} },
{ "network", "clearbanned", clearbanned, {} },
{ "network", "setnetworkactive", setnetworkactive, {"state"} },
+ { "network", "getnodeaddresses", getnodeaddresses, {"count"} },
};
// clang-format on
void RegisterNetRPCCommands(CRPCTable &t) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 79f1199ca..45e880ddf 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -1,119 +1,160 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test RPC calls related to net.
Tests correspond to code in rpc/net.cpp.
"""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
+ assert_greater_than,
assert_raises_rpc_error,
connect_nodes_bi,
p2p_port,
wait_until,
)
+from test_framework.mininode import P2PInterface
+from test_framework.messages import CAddress, msg_addr, NODE_NETWORK
class NetTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [["-minrelaytxfee=0.00001000"],
["-minrelaytxfee=0.00000500"]]
def run_test(self):
self._test_connection_count()
self._test_getnettotals()
self._test_getnetworkinginfo()
self._test_getaddednodeinfo()
self._test_getpeerinfo()
+ self._test_getnodeaddresses()
def _test_connection_count(self):
# connect_nodes_bi connects each node to the other
assert_equal(self.nodes[0].getconnectioncount(), 2)
def _test_getnettotals(self):
# getnettotals totalbytesrecv and totalbytessent should be
# consistent with getpeerinfo. Since the RPC calls are not atomic,
# and messages might have been recvd or sent between RPC calls, call
# getnettotals before and after and verify that the returned values
# from getpeerinfo are bounded by those values.
net_totals_before = self.nodes[0].getnettotals()
peer_info = self.nodes[0].getpeerinfo()
net_totals_after = self.nodes[0].getnettotals()
assert_equal(len(peer_info), 2)
peers_recv = sum([peer['bytesrecv'] for peer in peer_info])
peers_sent = sum([peer['bytessent'] for peer in peer_info])
assert_greater_than_or_equal(
peers_recv, net_totals_before['totalbytesrecv'])
assert_greater_than_or_equal(
net_totals_after['totalbytesrecv'], peers_recv)
assert_greater_than_or_equal(
peers_sent, net_totals_before['totalbytessent'])
assert_greater_than_or_equal(
net_totals_after['totalbytessent'], peers_sent)
# test getnettotals and getpeerinfo by doing a ping
# the bytes sent/received should change
# note ping and pong are 32 bytes each
self.nodes[0].ping()
wait_until(lambda: (self.nodes[0].getnettotals()[
'totalbytessent'] >= net_totals_after['totalbytessent'] + 32 * 2), timeout=1)
wait_until(lambda: (self.nodes[0].getnettotals()[
'totalbytesrecv'] >= net_totals_after['totalbytesrecv'] + 32 * 2), timeout=1)
peer_info_after_ping = self.nodes[0].getpeerinfo()
for before, after in zip(peer_info, peer_info_after_ping):
assert_greater_than_or_equal(
after['bytesrecv_per_msg']['pong'], before['bytesrecv_per_msg']['pong'] + 32)
assert_greater_than_or_equal(
after['bytessent_per_msg']['ping'], before['bytessent_per_msg']['ping'] + 32)
def _test_getnetworkinginfo(self):
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
self.nodes[0].setnetworkactive(False)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False)
# Wait a bit for all sockets to close
wait_until(lambda: self.nodes[0].getnetworkinfo()[
'connections'] == 0, timeout=3)
self.nodes[0].setnetworkactive(True)
connect_nodes_bi(self.nodes[0], self.nodes[1])
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
def _test_getaddednodeinfo(self):
assert_equal(self.nodes[0].getaddednodeinfo(), [])
# add a node (node2) to node0
ip_port = "127.0.0.1:{}".format(p2p_port(2))
self.nodes[0].addnode(ip_port, 'add')
# check that the node has indeed been added
added_nodes = self.nodes[0].getaddednodeinfo(ip_port)
assert_equal(len(added_nodes), 1)
assert_equal(added_nodes[0]['addednode'], ip_port)
# check that a non-existent node returns an error
assert_raises_rpc_error(-24, "Node has not been added",
self.nodes[0].getaddednodeinfo, '1.1.1.1')
def _test_getpeerinfo(self):
peer_info = [x.getpeerinfo() for x in self.nodes]
# check both sides of bidirectional connection between nodes
# the address bound to on one side will be the source address for the
# other node
assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr'])
assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr'])
assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500"))
assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000"))
+ def _test_getnodeaddresses(self):
+ self.nodes[0].add_p2p_connection(P2PInterface())
+
+ # send some addresses to the node via the p2p message addr
+ msg = msg_addr()
+ imported_addrs = []
+ for i in range(256):
+ a = "123.123.123.{}".format(i)
+ imported_addrs.append(a)
+ addr = CAddress()
+ addr.time = 100000000
+ addr.nServices = NODE_NETWORK
+ addr.ip = a
+ addr.port = 8333
+ msg.addrs.append(addr)
+ self.nodes[0].p2p.send_and_ping(msg)
+
+ # obtain addresses via rpc call and check they were ones sent in before
+ REQUEST_COUNT = 10
+ node_addresses = self.nodes[0].getnodeaddresses(REQUEST_COUNT)
+ assert_equal(len(node_addresses), REQUEST_COUNT)
+ for a in node_addresses:
+ assert_greater_than(a["time"], 1527811200) # 1st June 2018
+ assert_equal(a["services"], NODE_NETWORK)
+ assert a["address"] in imported_addrs
+ assert_equal(a["port"], 8333)
+
+ assert_raises_rpc_error(-8, "Address count out of range",
+ self.nodes[0].getnodeaddresses, -1)
+
+ # addrman's size cannot be known reliably after insertion, as hash collisions may occur
+ # so only test that requesting a large number of addresses returns less
+ # than that
+ LARGE_REQUEST_COUNT = 10000
+ node_addresses = self.nodes[0].getnodeaddresses(LARGE_REQUEST_COUNT)
+ assert_greater_than(LARGE_REQUEST_COUNT, len(node_addresses))
+
if __name__ == '__main__':
NetTest().main()