diff --git a/doc/release-notes.md b/doc/release-notes.md
index ea88da78f..c7aa95d60 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,10 +1,17 @@
# Bitcoin ABC 0.24.3 Release Notes
Bitcoin ABC version 0.24.3 is now available from:
This release includes the following features and fixes:
- The `-zapwallettxes` startup option has been removed and its functionality removed
from the wallet. This functionality has been superseded with the abandon transaction
feature.
+ - `getnetworkinfo` now returns two new fields, `connections_in` and
+ `connections_out`, that provide the number of inbound and outbound peer
+ connections. These new fields are in addition to the existing `connections`
+ field, which returns the total number of peer connections.
+ - The `connections` field of `bitcoin-cli -getinfo` is expanded to return a JSON
+ object with `in`, `out` and `total` numbers of peer connections. It previously
+ returned a single integer value for the total number of peer connections.
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 422f056ba..75ab00549 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -1,1149 +1,1157 @@
// Copyright (c) 2009-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.
#if defined(HAVE_CONFIG_H)
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const std::function G_TRANSLATION_FUN = nullptr;
static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
static const int DEFAULT_HTTP_CLIENT_TIMEOUT = 900;
static const bool DEFAULT_NAMED = false;
static const int CONTINUE_EXECUTION = -1;
static const std::string ONION{".onion"};
static const size_t ONION_LEN{ONION.size()};
/** Default number of blocks to generate for RPC generatetoaddress. */
static const std::string DEFAULT_NBLOCKS = "1";
static void SetupCliArgs(ArgsManager &argsman) {
SetupHelpOptions(argsman);
const auto defaultBaseParams =
CreateBaseChainParams(CBaseChainParams::MAIN);
const auto testnetBaseParams =
CreateBaseChainParams(CBaseChainParams::TESTNET);
const auto regtestBaseParams =
CreateBaseChainParams(CBaseChainParams::REGTEST);
SetupCurrencyUnitOptions(argsman);
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY,
OptionsCategory::OPTIONS);
argsman.AddArg(
"-conf=",
strprintf("Specify configuration file. Relative paths will be "
"prefixed by datadir location. (default: %s)",
BITCOIN_CONF_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=", "Specify data directory",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-generate",
strprintf(
"Generate blocks immediately, equivalent to RPC generatenewaddress "
"followed by RPC generatetoaddress. Optional positional integer "
"arguments are number of blocks to generate (default: %s) and "
"maximum iterations to try (default: %s), equivalent to RPC "
"generatetoaddress nblocks and maxtries arguments. Example: "
"bitcoin-cli -generate 4 1000",
DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-getinfo",
"Get general information from the remote server. Note that unlike "
"server-side RPC calls, the results of -getinfo is the result of "
"multiple non-atomic requests. Some entries in the result may "
"represent results from different states (e.g. wallet balance may be "
"as of a different block from the chain state reported)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-netinfo",
"Get network peer connection information from the remote "
"server. An optional integer argument from 0 to 4 can be "
"passed for different peers listings (default: 0).",
ArgsManager::ALLOW_INT, OptionsCategory::OPTIONS);
SetupChainParamsBaseOptions(argsman);
argsman.AddArg(
"-named",
strprintf("Pass named instead of positional arguments (default: %s)",
DEFAULT_NAMED),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-rpcconnect=",
strprintf("Send commands to node running on (default: %s)",
DEFAULT_RPCCONNECT),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-rpccookiefile=",
"Location of the auth cookie. Relative paths will be prefixed "
"by a net-specific datadir location. (default: data dir)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcport=",
strprintf("Connect to JSON-RPC on (default: %u, "
"testnet: %u, regtest: %u)",
defaultBaseParams->RPCPort(),
testnetBaseParams->RPCPort(),
regtestBaseParams->RPCPort()),
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::OPTIONS);
argsman.AddArg("-rpcwait", "Wait for RPC server to start",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcuser=", "Username for JSON-RPC connections",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-rpcclienttimeout=",
strprintf("Timeout in seconds during HTTP requests, or 0 for "
"no timeout. (default: %d)",
DEFAULT_HTTP_CLIENT_TIMEOUT),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-stdinrpcpass",
"Read RPC password from standard input as a single "
"line. When combined with -stdin, the first line "
"from standard input is used for the RPC password. When "
"combined with -stdinwalletpassphrase, -stdinrpcpass "
"consumes the first line, and -stdinwalletpassphrase "
"consumes the second.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-stdinwalletpassphrase",
"Read wallet passphrase from standard input as a single "
"line. When combined with -stdin, the first line "
"from standard input is used for the wallet passphrase.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-stdin",
"Read extra arguments from standard input, one per line until "
"EOF/Ctrl-D (recommended for sensitive information such as "
"passphrases). When combined with -stdinrpcpass, the first "
"line from standard input is used for the RPC password.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-rpcwallet=",
"Send RPC for non-default wallet on RPC server (needs to exactly match "
"corresponding -wallet option passed to bitcoind). This changes the "
"RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
}
/** libevent event log callback */
static void libevent_log_cb(int severity, const char *msg) {
#ifndef EVENT_LOG_ERR
// EVENT_LOG_ERR was added in 2.0.19; but before then _EVENT_LOG_ERR existed.
#define EVENT_LOG_ERR _EVENT_LOG_ERR
#endif
// Ignore everything other than errors
if (severity >= EVENT_LOG_ERR) {
throw std::runtime_error(strprintf("libevent error: %s", msg));
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Start
//
//
// Exception thrown on connection error. This error is used to determine when
// to wait if -rpcwait is given.
//
class CConnectionFailed : public std::runtime_error {
public:
explicit inline CConnectionFailed(const std::string &msg)
: std::runtime_error(msg) {}
};
//
// This function returns either one of EXIT_ codes when it's expected to stop
// the process or CONTINUE_EXECUTION when it's expected to continue further.
//
static int AppInitRPC(int argc, char *argv[]) {
//
// Parameters
//
SetupCliArgs(gArgs);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n",
error);
return EXIT_FAILURE;
}
if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
std::string strUsage =
PACKAGE_NAME " RPC client version " + FormatFullVersion() + "\n";
if (!gArgs.IsArgSet("-version")) {
strUsage += "\n"
"Usage: bitcoin-cli [options] [params] "
"Send command to " PACKAGE_NAME "\n"
"or: bitcoin-cli [options] -named "
"[name=value]... Send command to " PACKAGE_NAME
" (with named arguments)\n"
"or: bitcoin-cli [options] help "
"List commands\n"
"or: bitcoin-cli [options] help Get "
"help for a command\n";
strUsage += "\n" + gArgs.GetHelpMessage();
}
tfm::format(std::cout, "%s", strUsage);
if (argc < 2) {
tfm::format(std::cerr, "Error: too few parameters\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
if (!CheckDataDirOption()) {
tfm::format(std::cerr,
"Error: Specified data directory \"%s\" does not exist.\n",
gArgs.GetArg("-datadir", ""));
return EXIT_FAILURE;
}
if (!gArgs.ReadConfigFiles(error, true)) {
tfm::format(std::cerr, "Error reading configuration file: %s\n", error);
return EXIT_FAILURE;
}
// Check for -chain, -testnet or -regtest parameter (BaseParams() calls are
// only valid after this clause)
try {
SelectBaseParams(gArgs.GetChainName());
} catch (const std::exception &e) {
tfm::format(std::cerr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
return CONTINUE_EXECUTION;
}
/** Reply structure for request_done to fill in */
struct HTTPReply {
HTTPReply() : status(0), error(-1) {}
int status;
int error;
std::string body;
};
static std::string http_errorstring(int code) {
switch (code) {
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
case EVREQ_HTTP_TIMEOUT:
return "timeout reached";
case EVREQ_HTTP_EOF:
return "EOF reached";
case EVREQ_HTTP_INVALID_HEADER:
return "error while reading header, or invalid header";
case EVREQ_HTTP_BUFFER_ERROR:
return "error encountered while reading or writing";
case EVREQ_HTTP_REQUEST_CANCEL:
return "request was canceled";
case EVREQ_HTTP_DATA_TOO_LONG:
return "response body is larger than allowed";
#endif
default:
return "unknown";
}
}
static void http_request_done(struct evhttp_request *req, void *ctx) {
HTTPReply *reply = static_cast(ctx);
if (req == nullptr) {
/**
* If req is nullptr, it means an error occurred while connecting: the
* error code will have been passed to http_error_cb.
*/
reply->status = 0;
return;
}
reply->status = evhttp_request_get_response_code(req);
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
if (buf) {
size_t size = evbuffer_get_length(buf);
const char *data = (const char *)evbuffer_pullup(buf, size);
if (data) {
reply->body = std::string(data, size);
}
evbuffer_drain(buf, size);
}
}
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
static void http_error_cb(enum evhttp_request_error err, void *ctx) {
HTTPReply *reply = static_cast(ctx);
reply->error = err;
}
#endif
/**
* Class that handles the conversion from a command-line to a JSON-RPC request,
* as well as converting back to a JSON object that can be shown as result.
*/
class BaseRequestHandler {
public:
virtual ~BaseRequestHandler() {}
virtual UniValue PrepareRequest(const std::string &method,
const std::vector &args) = 0;
virtual UniValue ProcessReply(const UniValue &batch_in) = 0;
};
/** Process getinfo requests */
class GetinfoRequestHandler : public BaseRequestHandler {
public:
const int ID_NETWORKINFO = 0;
const int ID_BLOCKCHAININFO = 1;
const int ID_WALLETINFO = 2;
const int ID_BALANCES = 3;
/** Create a simulated `getinfo` request. */
UniValue PrepareRequest(const std::string &method,
const std::vector &args) override {
if (!args.empty()) {
throw std::runtime_error("-getinfo takes no arguments");
}
UniValue result(UniValue::VARR);
result.push_back(
JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue,
ID_BLOCKCHAININFO));
result.push_back(
JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
result.push_back(
JSONRPCRequestObj("getbalances", NullUniValue, ID_BALANCES));
return result;
}
/** Collect values from the batch and form a simulated `getinfo` reply. */
UniValue ProcessReply(const UniValue &batch_in) override {
UniValue result(UniValue::VOBJ);
const std::vector batch = JSONRPCProcessBatchReply(batch_in);
// Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass
// them on; getwalletinfo() and getbalances are allowed to fail if there
// is no wallet.
if (!batch[ID_NETWORKINFO]["error"].isNull()) {
return batch[ID_NETWORKINFO];
}
if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {
return batch[ID_BLOCKCHAININFO];
}
result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);
result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);
result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]);
result.pushKV(
"verificationprogress",
batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]);
result.pushKV("timeoffset",
batch[ID_NETWORKINFO]["result"]["timeoffset"]);
- result.pushKV("connections",
- batch[ID_NETWORKINFO]["result"]["connections"]);
+
+ UniValue connections(UniValue::VOBJ);
+ connections.pushKV("in",
+ batch[ID_NETWORKINFO]["result"]["connections_in"]);
+ connections.pushKV("out",
+ batch[ID_NETWORKINFO]["result"]["connections_out"]);
+ connections.pushKV("total",
+ batch[ID_NETWORKINFO]["result"]["connections"]);
+ result.pushKV("connections", connections);
+
result.pushKV("proxy",
batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]);
result.pushKV("difficulty",
batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
result.pushKV("chain",
UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"]));
if (!batch[ID_WALLETINFO]["result"].isNull()) {
result.pushKV("keypoolsize",
batch[ID_WALLETINFO]["result"]["keypoolsize"]);
if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
result.pushKV("unlocked_until",
batch[ID_WALLETINFO]["result"]["unlocked_until"]);
}
result.pushKV("paytxfee",
batch[ID_WALLETINFO]["result"]["paytxfee"]);
}
if (!batch[ID_BALANCES]["result"].isNull()) {
result.pushKV("balance",
batch[ID_BALANCES]["result"]["mine"]["trusted"]);
}
result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
return JSONRPCReplyObj(result, NullUniValue, 1);
}
};
/** Process netinfo requests */
class NetinfoRequestHandler : public BaseRequestHandler {
private:
bool IsAddrIPv6(const std::string &addr) const {
return !addr.empty() && addr.front() == '[';
}
bool IsInboundOnion(const std::string &addr_local, int mapped_as) const {
return mapped_as == 0 && addr_local.find(ONION) != std::string::npos;
}
bool IsOutboundOnion(const std::string &addr, int mapped_as) const {
const size_t addr_len{addr.size()};
const size_t onion_pos{addr.rfind(ONION)};
return mapped_as == 0 && onion_pos != std::string::npos &&
addr_len > ONION_LEN &&
(onion_pos == addr_len - ONION_LEN ||
onion_pos == addr.find_last_of(":") - ONION_LEN);
}
//! Optional user-supplied arg to set dashboard details level
uint8_t m_details_level{0};
bool DetailsRequested() const {
return m_details_level > 0 && m_details_level < 5;
}
bool IsAddressSelected() const {
return m_details_level == 2 || m_details_level == 4;
}
bool IsVersionSelected() const {
return m_details_level == 3 || m_details_level == 4;
}
enum struct NetType {
ipv4,
ipv6,
onion,
};
struct Peer {
int id;
int mapped_as;
int version;
int64_t conn_time;
int64_t last_blck;
int64_t last_recv;
int64_t last_send;
int64_t last_trxn;
double min_ping;
double ping;
std::string addr;
std::string sub_version;
NetType net_type;
bool is_block_relay;
bool is_outbound;
bool operator<(const Peer &rhs) const {
return std::tie(is_outbound, min_ping) <
std::tie(rhs.is_outbound, rhs.min_ping);
}
};
std::string NetTypeEnumToString(NetType t) {
switch (t) {
case NetType::ipv4:
return "ipv4";
case NetType::ipv6:
return "ipv6";
case NetType::onion:
return "onion";
} // no default case, so the compiler can warn about missing cases
assert(false);
}
std::string ChainToString() const {
if (gArgs.GetChainName() == CBaseChainParams::TESTNET) {
return " testnet";
}
if (gArgs.GetChainName() == CBaseChainParams::REGTEST) {
return " regtest";
}
return "";
}
public:
const int ID_PEERINFO = 0;
const int ID_NETWORKINFO = 1;
UniValue PrepareRequest(const std::string &method,
const std::vector &args) override {
if (!args.empty()) {
uint8_t n{0};
if (ParseUInt8(args.at(0), &n)) {
m_details_level = n;
}
}
UniValue result(UniValue::VARR);
result.push_back(
JSONRPCRequestObj("getpeerinfo", NullUniValue, ID_PEERINFO));
result.push_back(
JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
return result;
}
UniValue ProcessReply(const UniValue &batch_in) override {
const std::vector batch{JSONRPCProcessBatchReply(batch_in)};
if (!batch[ID_PEERINFO]["error"].isNull()) {
return batch[ID_PEERINFO];
}
if (!batch[ID_NETWORKINFO]["error"].isNull()) {
return batch[ID_NETWORKINFO];
}
const UniValue &networkinfo{batch[ID_NETWORKINFO]["result"]};
if (networkinfo["version"].get_int() < 230000) {
throw std::runtime_error("-netinfo requires bitcoind server to be "
"running v0.23.0 and up");
}
// Count peer connection totals, and if DetailsRequested(), store peer
// data in a vector of structs.
const int64_t time_now{GetSystemTimeInSeconds()};
// inbound conn counters
int ipv4_i{0}, ipv6_i{0}, onion_i{0}, block_relay_i{0}, total_i{0};
// outbound conn counters
int ipv4_o{0}, ipv6_o{0}, onion_o{0}, block_relay_o{0}, total_o{0};
size_t max_peer_id_length{2}, max_addr_length{0};
bool is_asmap_on{false};
std::vector peers;
const UniValue &getpeerinfo{batch[ID_PEERINFO]["result"]};
for (const UniValue &peer : getpeerinfo.getValues()) {
const std::string addr{peer["addr"].get_str()};
const std::string addr_local{
peer["addrlocal"].isNull() ? "" : peer["addrlocal"].get_str()};
const int mapped_as{
peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()};
const bool is_block_relay{!peer["relaytxes"].get_bool()};
const bool is_inbound{peer["inbound"].get_bool()};
NetType net_type{NetType::ipv4};
if (is_inbound) {
if (IsAddrIPv6(addr)) {
net_type = NetType::ipv6;
++ipv6_i;
} else if (IsInboundOnion(addr_local, mapped_as)) {
net_type = NetType::onion;
++onion_i;
} else {
++ipv4_i;
}
if (is_block_relay) {
++block_relay_i;
}
} else {
if (IsAddrIPv6(addr)) {
net_type = NetType::ipv6;
++ipv6_o;
} else if (IsOutboundOnion(addr, mapped_as)) {
net_type = NetType::onion;
++onion_o;
} else {
++ipv4_o;
}
if (is_block_relay) {
++block_relay_o;
}
}
if (DetailsRequested()) {
// Push data for this peer to the peers vector.
const int peer_id{peer["id"].get_int()};
const int version{peer["version"].get_int()};
const std::string sub_version{peer["subver"].get_str()};
const int64_t conn_time{peer["conntime"].get_int64()};
const int64_t last_blck{peer["last_block"].get_int64()};
const int64_t last_recv{peer["lastrecv"].get_int64()};
const int64_t last_send{peer["lastsend"].get_int64()};
const int64_t last_trxn{peer["last_transaction"].get_int64()};
const double min_ping{
peer["minping"].isNull() ? -1 : peer["minping"].get_real()};
const double ping{peer["pingtime"].isNull()
? -1
: peer["pingtime"].get_real()};
peers.push_back({peer_id, mapped_as, version, conn_time,
last_blck, last_recv, last_send, last_trxn,
min_ping, ping, addr, sub_version, net_type,
is_block_relay, !is_inbound});
max_peer_id_length =
std::max(ToString(peer_id).length(), max_peer_id_length);
max_addr_length = std::max(addr.length() + 1, max_addr_length);
is_asmap_on |= (mapped_as != 0);
}
}
// Generate report header.
std::string result{strprintf("%s %s%s - %i%s\n\n", PACKAGE_NAME,
FormatFullVersion(), ChainToString(),
networkinfo["protocolversion"].get_int(),
networkinfo["subversion"].get_str())};
// Report detailed peer connections list sorted by direction and minimum
// ping time.
if (DetailsRequested() && !peers.empty()) {
std::sort(peers.begin(), peers.end());
result += "Peer connections sorted by direction and min ping\n<-> "
"relay net mping ping send recv txn blk uptime ";
if (is_asmap_on) {
result += " asmap ";
}
result += strprintf("%*s %-*s%s\n", max_peer_id_length, "id",
IsAddressSelected() ? max_addr_length : 0,
IsAddressSelected() ? "address" : "",
IsVersionSelected() ? "version" : "");
for (const Peer &peer : peers) {
std::string version{ToString(peer.version) + peer.sub_version};
result += strprintf(
"%3s %5s %5s%6s%7s%5s%5s%5s%5s%7s%*i %*s %-*s%s\n",
peer.is_outbound ? "out" : "in",
peer.is_block_relay ? "block" : "full",
NetTypeEnumToString(peer.net_type),
peer.min_ping == -1 ? ""
: ToString(round(1000 * peer.min_ping)),
peer.ping == -1 ? "" : ToString(round(1000 * peer.ping)),
peer.last_send == 0 ? ""
: ToString(time_now - peer.last_send),
peer.last_recv == 0 ? ""
: ToString(time_now - peer.last_recv),
peer.last_trxn == 0
? ""
: ToString((time_now - peer.last_trxn) / 60),
peer.last_blck == 0
? ""
: ToString((time_now - peer.last_blck) / 60),
peer.conn_time == 0
? ""
: ToString((time_now - peer.conn_time) / 60),
// variable spacing
is_asmap_on ? 7 : 0,
is_asmap_on && peer.mapped_as != 0
? ToString(peer.mapped_as)
: "",
// variable spacing
max_peer_id_length, peer.id,
// variable spacing
IsAddressSelected() ? max_addr_length : 0,
IsAddressSelected() ? peer.addr : "",
IsVersionSelected() && version != "0" ? version : "");
}
result +=
" ms ms sec sec min min min\n\n";
}
// Report peer connection totals by type.
total_i = ipv4_i + ipv6_i + onion_i;
total_o = ipv4_o + ipv6_o + onion_o;
result += " ipv4 ipv6 onion total block-relay\n";
result += strprintf("in %5i %5i %5i %5i %5i\n", ipv4_i,
ipv6_i, onion_i, total_i, block_relay_i);
result += strprintf("out %5i %5i %5i %5i %5i\n", ipv4_o,
ipv6_o, onion_o, total_o, block_relay_o);
result += strprintf("total %5i %5i %5i %5i %5i\n",
ipv4_i + ipv4_o, ipv6_i + ipv6_o, onion_i + onion_o,
total_i + total_o, block_relay_i + block_relay_o);
// Report local addresses, ports, and scores.
result += "\nLocal addresses";
const UniValue &local_addrs{networkinfo["localaddresses"]};
if (local_addrs.empty()) {
result += ": n/a\n";
} else {
for (const UniValue &addr : local_addrs.getValues()) {
result +=
strprintf("\n%-40i port %5i score %6i",
addr["address"].get_str(), addr["port"].get_int(),
addr["score"].get_int());
}
}
return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1);
}
};
/** Process RPC generatetoaddress request. */
class GenerateToAddressRequestHandler : public BaseRequestHandler {
public:
UniValue PrepareRequest(const std::string &method,
const std::vector &args) override {
address_str = args.at(1);
UniValue params{RPCConvertValues("generatetoaddress", args)};
return JSONRPCRequestObj("generatetoaddress", params, 1);
}
UniValue ProcessReply(const UniValue &reply) override {
UniValue result(UniValue::VOBJ);
result.pushKV("address", address_str);
result.pushKV("blocks", reply.get_obj()["result"]);
return JSONRPCReplyObj(result, NullUniValue, 1);
}
protected:
std::string address_str;
};
/** Process default single requests */
class DefaultRequestHandler : public BaseRequestHandler {
public:
UniValue PrepareRequest(const std::string &method,
const std::vector &args) override {
UniValue params;
if (gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
params = RPCConvertNamedValues(method, args);
} else {
params = RPCConvertValues(method, args);
}
return JSONRPCRequestObj(method, params, 1);
}
UniValue ProcessReply(const UniValue &reply) override {
return reply.get_obj();
}
};
static UniValue CallRPC(BaseRequestHandler *rh, const std::string &strMethod,
const std::vector &args,
const std::optional &rpcwallet = {}) {
std::string host;
// In preference order, we choose the following for the port:
// 1. -rpcport
// 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6)
// 3. default port for chain
int port = BaseParams().RPCPort();
SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host);
port = gArgs.GetArg("-rpcport", port);
// Obtain event base
raii_event_base base = obtain_event_base();
// Synchronously look up hostname
raii_evhttp_connection evcon =
obtain_evhttp_connection_base(base.get(), host, port);
// Set connection timeout
{
const int timeout =
gArgs.GetArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
if (timeout > 0) {
evhttp_connection_set_timeout(evcon.get(), timeout);
} else {
// Indefinite request timeouts are not possible in libevent-http,
// so we set the timeout to a very long time period instead.
// Average length of year in Gregorian calendar
constexpr int YEAR_IN_SECONDS = 31556952;
evhttp_connection_set_timeout(evcon.get(), 5 * YEAR_IN_SECONDS);
}
}
HTTPReply response;
raii_evhttp_request req =
obtain_evhttp_request(http_request_done, (void *)&response);
if (req == nullptr) {
throw std::runtime_error("create http request failed");
}
#if LIBEVENT_VERSION_NUMBER >= 0x02010300
evhttp_request_set_error_cb(req.get(), http_error_cb);
#endif
// Get credentials
std::string strRPCUserColonPass;
bool failedToGetAuthCookie = false;
if (gArgs.GetArg("-rpcpassword", "") == "") {
// Try fall back to cookie-based authentication if no password is
// provided
if (!GetAuthCookie(&strRPCUserColonPass)) {
failedToGetAuthCookie = true;
}
} else {
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" +
gArgs.GetArg("-rpcpassword", "");
}
struct evkeyvalq *output_headers =
evhttp_request_get_output_headers(req.get());
assert(output_headers);
evhttp_add_header(output_headers, "Host", host.c_str());
evhttp_add_header(output_headers, "Connection", "close");
evhttp_add_header(
output_headers, "Authorization",
(std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
// Attach request data
std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
struct evbuffer *output_buffer =
evhttp_request_get_output_buffer(req.get());
assert(output_buffer);
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
// check if we should use a special wallet endpoint
std::string endpoint = "/";
if (rpcwallet) {
char *encodedURI =
evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
if (encodedURI) {
endpoint = "/wallet/" + std::string(encodedURI);
free(encodedURI);
} else {
throw CConnectionFailed("uri-encode failed");
}
}
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST,
endpoint.c_str());
// ownership moved to evcon in above call
req.release();
if (r != 0) {
throw CConnectionFailed("send http request failed");
}
event_base_dispatch(base.get());
if (response.status == 0) {
std::string responseErrorMessage;
if (response.error != -1) {
responseErrorMessage =
strprintf(" (error code %d - \"%s\")", response.error,
http_errorstring(response.error));
}
throw CConnectionFailed(
strprintf("Could not connect to the server %s:%d%s\n\nMake sure "
"the bitcoind server is running and that you are "
"connecting to the correct RPC port.",
host, port, responseErrorMessage));
} else if (response.status == HTTP_UNAUTHORIZED) {
if (failedToGetAuthCookie) {
throw std::runtime_error(strprintf(
"Could not locate RPC credentials. No authentication cookie "
"could be found, and RPC password is not set. See "
"-rpcpassword and -stdinrpcpass. Configuration file: (%s)",
GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME))
.string()));
} else {
throw std::runtime_error(
"Authorization failed: Incorrect rpcuser or rpcpassword");
}
} else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST &&
response.status != HTTP_NOT_FOUND &&
response.status != HTTP_INTERNAL_SERVER_ERROR) {
throw std::runtime_error(
strprintf("server returned HTTP error %d", response.status));
} else if (response.body.empty()) {
throw std::runtime_error("no response from server");
}
// Parse reply
UniValue valReply(UniValue::VSTR);
if (!valReply.read(response.body)) {
throw std::runtime_error("couldn't parse reply from server");
}
const UniValue reply = rh->ProcessReply(valReply);
if (reply.empty()) {
throw std::runtime_error(
"expected reply to have result, error and id properties");
}
return reply;
}
/**
* ConnectAndCallRPC wraps CallRPC with -rpcwait and an exception handler.
*
* @param[in] rh Pointer to RequestHandler.
* @param[in] strMethod Reference to const string method to forward to CallRPC.
* @param[in] rpcwallet Reference to const optional string wallet name to
* forward to CallRPC.
* @returns the RPC response as a UniValue object.
* @throws a CConnectionFailed std::runtime_error if connection failed or RPC
* server still in warmup.
*/
static UniValue
ConnectAndCallRPC(BaseRequestHandler *rh, const std::string &strMethod,
const std::vector &args,
const std::optional &rpcwallet = {}) {
UniValue response(UniValue::VOBJ);
// Execute and handle connection failures with -rpcwait.
const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
do {
try {
response = CallRPC(rh, strMethod, args, rpcwallet);
if (fWait) {
const UniValue &error = find_value(response, "error");
if (!error.isNull() &&
error["code"].get_int() == RPC_IN_WARMUP) {
throw CConnectionFailed("server in warmup");
}
}
break; // Connection succeeded, no need to retry.
} catch (const CConnectionFailed &) {
if (fWait) {
UninterruptibleSleep(std::chrono::milliseconds{1000});
} else {
throw;
}
}
} while (fWait);
return response;
}
/** Parse UniValue result to update the message to print to std::cout. */
static void ParseResult(const UniValue &result, std::string &strPrint) {
if (result.isNull()) {
return;
}
strPrint = result.isStr() ? result.get_str() : result.write(2);
}
/**
* Parse UniValue error to update the message to print to std::cerr and the
* code to return.
*/
static void ParseError(const UniValue &error, std::string &strPrint,
int &nRet) {
if (error.isObject()) {
const UniValue &err_code = find_value(error, "code");
const UniValue &err_msg = find_value(error, "message");
if (!err_code.isNull()) {
strPrint = "error code: " + err_code.getValStr() + "\n";
}
if (err_msg.isStr()) {
strPrint += ("error message:\n" + err_msg.get_str());
}
if (err_code.isNum() &&
err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) {
strPrint += "\nTry adding \"-rpcwallet=\" option to "
"bitcoin-cli command line.";
}
} else {
strPrint = "error: " + error.write();
}
nRet = abs(error["code"].get_int());
}
/**
* GetWalletBalances calls listwallets; if more than one wallet is loaded, it
* then fetches mine.trusted balances for each loaded wallet and pushes them to
* `result`.
*
* @param result Reference to UniValue object the wallet names and balances are
* pushed to.
*/
static void GetWalletBalances(UniValue &result) {
DefaultRequestHandler rh;
const UniValue listwallets =
ConnectAndCallRPC(&rh, "listwallets", /* args=*/{});
if (!find_value(listwallets, "error").isNull()) {
return;
}
const UniValue &wallets = find_value(listwallets, "result");
if (wallets.size() <= 1) {
return;
}
UniValue balances(UniValue::VOBJ);
for (const UniValue &wallet : wallets.getValues()) {
const std::string wallet_name = wallet.get_str();
const UniValue getbalances =
ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
const UniValue &balance =
find_value(getbalances, "result")["mine"]["trusted"];
balances.pushKV(wallet_name, balance);
}
result.pushKV("balances", balances);
}
/**
* Call RPC getnewaddress.
* @returns getnewaddress response as a UniValue object.
*/
static UniValue GetNewAddress() {
std::optional wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) {
wallet_name = gArgs.GetArg("-rpcwallet", "");
}
DefaultRequestHandler rh;
return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
}
/**
* Check bounds and set up args for RPC generatetoaddress params: nblocks,
* address, maxtries.
* @param[in] address Reference to const string address to insert into the
* args.
* @param args Reference to vector of string args to modify.
*/
static void SetGenerateToAddressArgs(const std::string &address,
std::vector &args) {
if (args.size() > 2) {
throw std::runtime_error(
"too many arguments (maximum 2 for nblocks and maxtries)");
}
if (args.size() == 0) {
args.emplace_back(DEFAULT_NBLOCKS);
} else if (args.at(0) == "0") {
throw std::runtime_error(
"the first argument (number of blocks to generate, default: " +
DEFAULT_NBLOCKS + ") must be an integer value greater than zero");
}
args.emplace(args.begin() + 1, address);
}
static int CommandLineRPC(int argc, char *argv[]) {
std::string strPrint;
int nRet = 0;
try {
// Skip switches
while (argc > 1 && IsSwitchChar(argv[1][0])) {
argc--;
argv++;
}
std::string rpcPass;
if (gArgs.GetBoolArg("-stdinrpcpass", false)) {
NO_STDIN_ECHO();
if (!StdinReady()) {
fputs("RPC password> ", stderr);
fflush(stderr);
}
if (!std::getline(std::cin, rpcPass)) {
throw std::runtime_error("-stdinrpcpass specified but failed "
"to read from standard input");
}
if (StdinTerminal()) {
fputc('\n', stdout);
}
gArgs.ForceSetArg("-rpcpassword", rpcPass);
}
std::vector args =
std::vector(&argv[1], &argv[argc]);
if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) {
NO_STDIN_ECHO();
std::string walletPass;
if (args.size() < 1 ||
args[0].substr(0, 16) != "walletpassphrase") {
throw std::runtime_error(
"-stdinwalletpassphrase is only applicable for "
"walletpassphrase(change)");
}
if (!StdinReady()) {
fputs("Wallet passphrase> ", stderr);
fflush(stderr);
}
if (!std::getline(std::cin, walletPass)) {
throw std::runtime_error("-stdinwalletpassphrase specified but "
"failed to read from standard input");
}
if (StdinTerminal()) {
fputc('\n', stdout);
}
args.insert(args.begin() + 1, walletPass);
}
if (gArgs.GetBoolArg("-stdin", false)) {
// Read one arg per line from stdin and append
std::string line;
while (std::getline(std::cin, line)) {
args.push_back(line);
}
if (StdinTerminal()) {
fputc('\n', stdout);
}
}
std::unique_ptr rh;
std::string method;
if (gArgs.IsArgSet("-getinfo")) {
rh.reset(new GetinfoRequestHandler());
} else if (gArgs.GetBoolArg("-generate", false)) {
const UniValue getnewaddress{GetNewAddress()};
const UniValue &error{find_value(getnewaddress, "error")};
if (error.isNull()) {
SetGenerateToAddressArgs(
find_value(getnewaddress, "result").get_str(), args);
rh.reset(new GenerateToAddressRequestHandler());
} else {
ParseError(error, strPrint, nRet);
}
} else if (gArgs.GetBoolArg("-netinfo", false)) {
rh.reset(new NetinfoRequestHandler());
} else {
rh.reset(new DefaultRequestHandler());
if (args.size() < 1) {
throw std::runtime_error(
"too few parameters (need at least command)");
}
method = args[0];
// Remove trailing method name from arguments vector
args.erase(args.begin());
}
if (nRet == 0) {
// Perform RPC call
std::optional wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) {
wallet_name = gArgs.GetArg("-rpcwallet", "");
}
const UniValue reply =
ConnectAndCallRPC(rh.get(), method, args, wallet_name);
// Parse reply
UniValue result = find_value(reply, "result");
const UniValue &error = find_value(reply, "error");
if (error.isNull()) {
if (gArgs.IsArgSet("-getinfo") &&
!gArgs.IsArgSet("-rpcwallet")) {
// fetch multiwallet balances and append to result
GetWalletBalances(result);
}
ParseResult(result, strPrint);
} else {
ParseError(error, strPrint, nRet);
}
}
} catch (const std::exception &e) {
strPrint = std::string("error: ") + e.what();
nRet = EXIT_FAILURE;
} catch (...) {
PrintExceptionContinue(nullptr, "CommandLineRPC()");
throw;
}
if (strPrint != "") {
tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint);
}
return nRet;
}
int main(int argc, char *argv[]) {
#ifdef WIN32
util::WinCmdLineArgs winArgs;
std::tie(argc, argv) = winArgs.get();
#endif
SetupEnvironment();
if (!SetupNetworking()) {
tfm::format(std::cerr, "Error: Initializing networking failed\n");
return EXIT_FAILURE;
}
event_set_log_callback(&libevent_log_cb);
try {
int ret = AppInitRPC(argc, argv);
if (ret != CONTINUE_EXECUTION) {
return ret;
}
} catch (const std::exception &e) {
PrintExceptionContinue(&e, "AppInitRPC()");
return EXIT_FAILURE;
} catch (...) {
PrintExceptionContinue(nullptr, "AppInitRPC()");
return EXIT_FAILURE;
}
int ret = EXIT_FAILURE;
try {
ret = CommandLineRPC(argc, argv);
} catch (const std::exception &e) {
PrintExceptionContinue(&e, "CommandLineRPC()");
} catch (...) {
PrintExceptionContinue(nullptr, "CommandLineRPC()");
}
return ret;
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 81a8e9a0b..6e3516e32 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,1066 +1,1074 @@
// Copyright (c) 2009-2019 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 // For banmap_t
#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) {
RPCHelpMan{
"getconnectioncount",
"Returns the number of connections to other nodes.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The connection count"},
RPCExamples{HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
return int(node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL));
}
static UniValue ping(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"ping",
"Requests 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", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.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
node.connman->ForEachNode([](CNode *pnode) { pnode->fPingQueued = true; });
return NullUniValue;
}
static UniValue getpeerinfo(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getpeerinfo",
"Returns 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::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)\n"},
{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, "addnode",
"Whether connection was due to addnode/-connect or if it "
"was an automatic/inbound connection"},
{RPCResult::Type::NUM, "startingheight",
"The starting height (block) of the peer"},
{RPCResult::Type::NUM, "banscore",
"The ban score (DEPRECATED, returned only if config "
"option -deprecatedrpc=banscore is passed)"},
{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::BOOL, "whitelisted",
"Whether the peer is whitelisted"},
{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,
"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 of unknown message types "
"are listed under '" +
NET_MESSAGE_COMMAND_OTHER + "'."}}},
}},
}},
},
RPCExamples{HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector vstats;
node.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());
}
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);
if (g_avalanche) {
obj.pushKV("last_proof", stats.nLastProofTime);
}
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_ping_usec > 0) {
obj.pushKV("pingtime", double(stats.m_ping_usec) / 1e6);
}
if (stats.m_min_ping_usec < std::numeric_limits::max()) {
obj.pushKV("minping", double(stats.m_min_ping_usec) / 1e6);
}
if (stats.m_ping_wait_usec > 0) {
obj.pushKV("pingwait", double(stats.m_ping_wait_usec) / 1e6);
}
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) {
if (IsDeprecatedRPCEnabled(gArgs, "banscore")) {
// banscore is deprecated in v0.22.11 for removal in v0.23
obj.pushKV("banscore", statestats.m_misbehavior_score);
}
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.m_legacyWhitelisted);
UniValue permissions(UniValue::VARR);
for (const auto &permission :
NetPermissions::ToStrings(stats.m_permissionFlags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
obj.pushKV("minfeefilter", 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);
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(RPCHelpMan{
"addnode",
"Attempts 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",
{
{"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:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")},
}
.ToString());
}
NodeContext &node = EnsureNodeContext(request.context);
if (!node.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;
node.connman->OpenNetworkConnection(
addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL);
return NullUniValue;
}
if ((strCommand == "add") && (!node.connman->AddNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if ((strCommand == "remove") &&
(!node.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 UniValue disconnectnode(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"disconnectnode",
"Immediately 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,
/* default */ "fallback to nodeid",
"The IP address/port of the node"},
{"nodeid", RPCArg::Type::NUM,
/* default */ "fallback to address",
"The node ID (see getpeerinfo for node IDs)"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.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 = node.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 = node.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) {
RPCHelpMan{
"getaddednodeinfo",
"Returns information about the given added node, or all added nodes\n"
"(note that onetry addnodes are not listed here)\n",
{
{"node", RPCArg::Type::STR, /* default */ "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 bitcoin 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\"")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector vInfo = node.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) {
RPCHelpMan{
"getnettotals",
"Returns 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 time in milliseconds"},
+ "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", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("totalbytesrecv", node.connman->GetTotalBytesRecv());
obj.pushKV("totalbytessent", node.connman->GetTotalBytesSent());
obj.pushKV("timemillis", GetTimeMillis());
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.pushKV("timeframe", node.connman->GetMaxOutboundTimeframe());
outboundLimit.pushKV("target", node.connman->GetMaxOutboundTarget());
outboundLimit.pushKV("target_reached",
node.connman->OutboundTargetReached(false));
outboundLimit.pushKV("serve_historical_blocks",
!node.connman->OutboundTargetReached(true));
outboundLimit.pushKV("bytes_left_in_cycle",
node.connman->GetOutboundTargetBytesLeft());
outboundLimit.pushKV("time_left_in_cycle",
node.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) {
const auto &ticker = Currency::get().ticker;
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 number of 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 (ipv4, ipv6 or onion)"},
{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 for transactions in " + ticker + "/kB"},
{RPCResult::Type::NUM, "excessutxocharge",
"minimum charge for excess utxos in " + ticker},
{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", "")},
}
.Check(request);
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", userAgent(config));
obj.pushKV("protocolversion", PROTOCOL_VERSION);
NodeContext &node = EnsureNodeContext(request.context);
if (node.connman) {
ServiceFlags services = node.connman->GetLocalServices();
obj.pushKV("localservices", strprintf("%016x", services));
obj.pushKV("localservicesnames", GetServicesNames(services));
}
obj.pushKV("localrelay", g_relay_txes);
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
obj.pushKV("connections",
int(node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL)));
+ obj.pushKV("connections_in",
+ int(node.connman->GetNodeCount(CConnman::CONNECTIONS_IN)));
+ obj.pushKV("connections_out",
+ int(node.connman->GetNodeCount(CConnman::CONNECTIONS_OUT)));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ::minRelayTxFee.GetFeePerK());
obj.pushKV("excessutxocharge", 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(false).original);
return obj;
}
static UniValue setban(const Config &config, const JSONRPCRequest &request) {
const RPCHelpMan help{
"setban",
"Attempts 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, /* 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, /* 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")},
};
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || !help.IsValidNumArgs(request.params.size()) ||
(strCommand != "add" && strCommand != "remove")) {
throw std::runtime_error(help.ToString());
}
NodeContext &node = EnsureNodeContext(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");
}
// 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) {
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 UniValue listbanned(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"listbanned",
"List all manually banned IPs/Subnets.\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", ""},
{RPCResult::Type::NUM_TIME, "banned_until", ""},
{RPCResult::Type::NUM_TIME, "ban_created", ""},
{RPCResult::Type::STR, "ban_reason", ""},
}},
}},
RPCExamples{HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
banmap_t banMap;
node.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);
bannedAddresses.push_back(rec);
}
return bannedAddresses;
}
static UniValue clearbanned(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"clearbanned",
"Clear all banned IPs.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", "")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.banman->ClearBanned();
return NullUniValue;
}
static UniValue setnetworkactive(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"setnetworkactive",
"Disable/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{""},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.connman->SetNetworkActive(request.params[0].get_bool());
return node.connman->GetNetworkActive();
}
static UniValue getnodeaddresses(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getnodeaddresses",
"Return known addresses which can potentially be used to find new "
"nodes in the network\n",
{
{"count", RPCArg::Type::NUM, /* default */ "1",
"The maximum number of addresses to return. Specify 0 to return "
"all known addresses."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The " + UNIX_EPOCH_TIME +
" of when the node was last seen"},
{RPCResult::Type::NUM, "services", "The services offered"},
{RPCResult::Type::STR, "address",
"The address of the node"},
{RPCResult::Type::NUM, "port", "The port of the node"},
}},
}},
RPCExamples{HelpExampleCli("getnodeaddresses", "8") +
HelpExampleRpc("getnodeaddresses", "8")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.banman) {
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 =
node.connman->GetAddresses(count, /* max_pct */ 0);
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());
ret.push_back(obj);
}
return ret;
}
static UniValue addpeeraddress(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"addpeeraddress",
"Add 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\" 8333") +
HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333")},
}
.Check(request);
NodeContext &node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
UniValue obj(UniValue::VOBJ);
std::string addr_string = request.params[0].get_str();
uint16_t port = request.params[1].get_int();
CNetAddr net_addr;
if (!LookupHost(addr_string, net_addr, false)) {
obj.pushKV("success", false);
return obj;
}
CAddress address = CAddress({net_addr, port}, ServiceFlags(NODE_NETWORK));
address.nTime = GetAdjustedTime();
// The source address is set equal to the address. This is equivalent to the
// peer announcing itself.
if (!node.connman->AddNewAddresses({address}, address)) {
obj.pushKV("success", false);
return obj;
}
obj.pushKV("success", true);
return obj;
}
void RegisterNetRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand 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"} },
{ "hidden", "addpeeraddress", addpeeraddress, {"address", "port"} },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index cb6befadc..4795ee877 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -1,326 +1,333 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2019 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 bitcoin-cli"""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_process_error,
assert_raises_rpc_error,
get_auth_cookie,
)
# The block reward of coinbaseoutput.nValue (50) BTC/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block.
BLOCKS = 101
BALANCE = (BLOCKS - 100) * 50000000
JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
WALLET_NOT_SPECIFIED = 'Wallet file not specified'
class TestBitcoinCli(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def skip_test_if_missing_module(self):
self.skip_if_no_cli()
def run_test(self):
"""Main test logic"""
self.nodes[0].generate(BLOCKS)
self.log.info(
"Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`")
cli_response = self.nodes[0].cli.getblockchaininfo()
rpc_response = self.nodes[0].getblockchaininfo()
assert_equal(cli_response, rpc_response)
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
self.log.info("Test -stdinrpcpass option")
assert_equal(BLOCKS, self.nodes[0].cli(
'-rpcuser={}'.format(user), '-stdinrpcpass', input=password).getblockcount())
assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli(
'-rpcuser={}'.format(user), '-stdinrpcpass', input="foo").echo)
self.log.info("Test -stdin and -stdinrpcpass")
assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser={}'.format(user),
'-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo())
assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli(
'-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input="foo").echo)
self.log.info("Test connecting to a non-existing server")
assert_raises_process_error(
1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
self.log.info("Test connecting with non-existing RPC cookie file")
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli(
'-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
self.log.info("Test -getinfo with arguments fails")
assert_raises_process_error(
1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
self.log.info(
"Test -getinfo returns expected network and blockchain info")
if self.is_wallet_compiled():
self.nodes[0].encryptwallet(password)
cli_get_info = self.nodes[0].cli().send_cli('-getinfo')
network_info = self.nodes[0].getnetworkinfo()
blockchain_info = self.nodes[0].getblockchaininfo()
assert_equal(cli_get_info['version'], network_info['version'])
assert_equal(cli_get_info['blocks'], blockchain_info['blocks'])
assert_equal(cli_get_info['headers'], blockchain_info['headers'])
assert_equal(cli_get_info['timeoffset'], network_info['timeoffset'])
- assert_equal(cli_get_info['connections'], network_info['connections'])
+ assert_equal(
+ cli_get_info['connections'],
+ {
+ 'in': network_info['connections_in'],
+ 'out': network_info['connections_out'],
+ 'total': network_info['connections']
+ }
+ )
assert_equal(cli_get_info['proxy'],
network_info['networks'][0]['proxy'])
assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty'])
assert_equal(cli_get_info['chain'], blockchain_info['chain'])
if self.is_wallet_compiled():
self.log.info(
"Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info")
assert_equal(cli_get_info['balance'], BALANCE)
assert 'balances' not in cli_get_info.keys()
wallet_info = self.nodes[0].getwalletinfo()
assert_equal(
cli_get_info['keypoolsize'],
wallet_info['keypoolsize'])
assert_equal(
cli_get_info['unlocked_until'],
wallet_info['unlocked_until'])
assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee'])
assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple
# wallets.
wallets = ['', 'Encrypted', 'secret']
amounts = [
BALANCE + Decimal('9999995.50'),
Decimal(9000000),
Decimal(31000000)]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
rpcwallet2 = '-rpcwallet={}'.format(wallets[1])
rpcwallet3 = '-rpcwallet={}'.format(wallets[2])
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
w1.sendtoaddress(w3.getnewaddress(), amounts[2])
# Mine a block to confirm; adds a block reward (50 BTC) to the
# default wallet.
self.nodes[0].generate(1)
self.log.info(
"Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
for i in range(len(wallets)):
cli_get_info = self.nodes[0].cli(
'-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli()
assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[i])
self.log.info(
"Test -getinfo with multiple wallets and "
"-rpcwallet=non-existing-wallet returns no balances")
cli_get_info_keys = self.nodes[0].cli(
'-getinfo', '-rpcwallet=does-not-exist').send_cli().keys()
assert 'balance' not in cli_get_info_keys
assert 'balances' not in cli_get_info_keys
self.log.info(
"Test -getinfo with multiple wallets returns all loaded "
"wallet names and balances")
assert_equal(set(self.nodes[0].listwallets()), set(wallets))
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
assert 'balance' not in cli_get_info.keys()
assert_equal(cli_get_info['balances'],
{k: v for k, v in zip(wallets, amounts)})
# Unload the default wallet and re-verify.
self.nodes[0].unloadwallet(wallets[0])
assert wallets[0] not in self.nodes[0].listwallets()
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
assert 'balance' not in cli_get_info.keys()
assert_equal(cli_get_info['balances'],
{k: v for k, v in zip(wallets[1:], amounts[1:])})
self.log.info(
"Test -getinfo after unloading all wallets except a "
"non-default one returns its balance")
self.nodes[0].unloadwallet(wallets[2])
assert_equal(self.nodes[0].listwallets(), [wallets[1]])
cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info(
"Test -getinfo with -rpcwallet=remaining-non-default-wallet"
" returns only its balance")
cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[1])
self.log.info(
"Test -getinfo with -rpcwallet=unloaded wallet returns"
" no balances")
cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
assert 'balance' not in cli_get_info_keys
assert 'balances' not in cli_get_info_keys
# Test bitcoin-cli -generate.
n1 = 3
n2 = 4
w2.walletpassphrase(password, self.rpc_timeout)
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate with no args')
generate = self.nodes[0].cli('-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate with bad args')
assert_raises_process_error(
1, JSON_PARSING_ERROR, self.nodes[0].cli(
'-generate', 'foo').echo)
assert_raises_process_error(
1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(
'-generate', 0).echo)
assert_raises_process_error(
1, TOO_MANY_ARGS, self.nodes[0].cli(
'-generate', 1, 2, 3).echo)
self.log.info('Test -generate with nblocks')
generate = self.nodes[0].cli('-generate', n1).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1)
self.log.info('Test -generate with nblocks and maxtries')
generate = self.nodes[0].cli('-generate', n2, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n2)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1 + n2)
self.log.info('Test -generate -rpcwallet in single-wallet mode')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 2 + n1 + n2)
self.log.info(
'Test -generate -rpcwallet=unloaded wallet raises RPC error')
assert_raises_rpc_error(-18,
WALLET_NOT_LOADED,
self.nodes[0].cli(rpcwallet3,
'-generate').echo)
assert_raises_rpc_error(-18,
WALLET_NOT_LOADED,
self.nodes[0].cli(rpcwallet3,
'-generate',
'foo').echo)
assert_raises_rpc_error(-18,
WALLET_NOT_LOADED,
self.nodes[0].cli(rpcwallet3,
'-generate',
0).echo)
assert_raises_rpc_error(-18,
WALLET_NOT_LOADED,
self.nodes[0].cli(rpcwallet3,
'-generate',
1,
2,
3).echo)
# Test bitcoin-cli -generate with -rpcwallet in multiwallet mode.
self.nodes[0].loadwallet(wallets[2])
n3 = 4
n4 = 10
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate -rpcwallet with no args')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate -rpcwallet with bad args')
assert_raises_process_error(
1, JSON_PARSING_ERROR, self.nodes[0].cli(
rpcwallet2, '-generate', 'foo').echo)
assert_raises_process_error(
1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(
rpcwallet2, '-generate', 0).echo)
assert_raises_process_error(
1, TOO_MANY_ARGS, self.nodes[0].cli(
rpcwallet2, '-generate', 1, 2, 3).echo)
self.log.info('Test -generate -rpcwallet with nblocks')
generate = self.nodes[0].cli(
rpcwallet2, '-generate', n3).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n3)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3)
self.log.info(
'Test -generate -rpcwallet with nblocks and maxtries')
generate = self.nodes[0].cli(
rpcwallet2, '-generate', n4, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n4)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4)
self.log.info(
'Test -generate without -rpcwallet in multiwallet mode raises RPC error')
assert_raises_rpc_error(-19,
WALLET_NOT_SPECIFIED,
self.nodes[0].cli('-generate').echo)
assert_raises_rpc_error(-19,
WALLET_NOT_SPECIFIED,
self.nodes[0].cli('-generate',
'foo').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED,
self.nodes[0].cli('-generate', 0).echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED,
self.nodes[0].cli('-generate', 1, 2, 3).echo)
else:
self.log.info(
"*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
# maintain block parity with the wallet_compiled conditional branch
self.nodes[0].generate(25)
self.log.info("Test -version with node stopped")
self.stop_node(0)
cli_response = self.nodes[0].cli().send_cli('-version')
assert "{} RPC client version".format(
self.config['environment']['PACKAGE_NAME']) in cli_response
self.log.info(
"Test -rpcwait option successfully waits for RPC connection")
# Start node without RPC connection.
self.nodes[0].start()
# ensure cookie file is available to avoid race condition
self.nodes[0].wait_for_cookie_credentials()
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
assert_equal(blocks, BLOCKS + 25)
if __name__ == '__main__':
TestBitcoinCli().main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 906054371..9d3eed966 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -1,266 +1,272 @@
#!/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 itertools import product
import time
from test_framework.avatools import create_coinbase_stakes
from test_framework.key import ECKey
from test_framework.p2p import P2PInterface
import test_framework.messages
from test_framework.messages import NODE_NETWORK
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
assert_greater_than_or_equal,
assert_greater_than,
assert_raises_rpc_error,
connect_nodes,
p2p_port,
)
from test_framework.wallet_util import bytes_to_wif
def assert_net_servicesnames(servicesflag, servicenames):
"""Utility that checks if all flags are correctly decoded in
`getpeerinfo` and `getnetworkinfo`.
:param servicesflag: The services as an integer.
:param servicenames: The list of decoded services names, as strings.
"""
servicesflag_generated = 0
for servicename in servicenames:
servicesflag_generated |= getattr(
test_framework.messages, 'NODE_' + servicename)
assert servicesflag_generated == servicesflag
class NetTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [["-enableavalanche=1", "-minrelaytxfee=10"],
["-enableavalanche=1", "-minrelaytxfee=5"]]
self.supports_cli = False
def run_test(self):
# Get out of IBD for the minfeefilter and getpeerinfo tests.
self.nodes[0].generate(101)
# Connect nodes both ways.
connect_nodes(self.nodes[0], self.nodes[1])
connect_nodes(self.nodes[1], self.nodes[0])
self.sync_all()
self.test_connection_count()
self.test_getpeerinfo()
self.test_getnettotals()
self.test_getnetworkinfo()
self.test_getaddednodeinfo()
self.test_service_flags()
self.test_getnodeaddresses()
def test_connection_count(self):
self.log.info("Test getconnectioncount")
# After using `connect_nodes` to connect nodes 0 and 1 to each other.
assert_equal(self.nodes[0].getconnectioncount(), 2)
def test_getnettotals(self):
self.log.info("Test getnettotals")
# 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()
self.wait_until(lambda: (self.nodes[0].getnettotals()[
'totalbytessent'] >= net_totals_after['totalbytessent'] + 32 * 2), timeout=1)
self.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'].get(
'pong', 0), before['bytesrecv_per_msg'].get(
'pong', 0) + 32)
assert_greater_than_or_equal(
after['bytessent_per_msg'].get(
'ping', 0), before['bytessent_per_msg'].get(
'ping', 0) + 32)
def test_getnetworkinfo(self):
self.log.info("Test getnetworkinfo")
- assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
- assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
+ info = self.nodes[0].getnetworkinfo()
+ assert_equal(info['networkactive'], True)
+ assert_equal(info['connections'], 2)
+ assert_equal(info['connections_in'], 1)
+ assert_equal(info['connections_out'], 1)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
self.nodes[0].setnetworkactive(state=False)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False)
# Wait a bit for all sockets to close
self.wait_until(lambda: self.nodes[0].getnetworkinfo()[
'connections'] == 0, timeout=3)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.nodes[0].setnetworkactive(state=True)
# Connect nodes both ways.
connect_nodes(self.nodes[0], self.nodes[1])
connect_nodes(self.nodes[1], self.nodes[0])
- assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
- assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
+ info = self.nodes[0].getnetworkinfo()
+ assert_equal(info['networkactive'], True)
+ assert_equal(info['connections'], 2)
+ assert_equal(info['connections_in'], 1)
+ assert_equal(info['connections_out'], 1)
# check the `servicesnames` field
network_info = [node.getnetworkinfo() for node in self.nodes]
for info in network_info:
assert_net_servicesnames(int(info["localservices"], 0x10),
info["localservicesnames"])
def test_getaddednodeinfo(self):
self.log.info("Test getaddednodeinfo")
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(node=ip_port, command='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 node cannot be added again
assert_raises_rpc_error(-23,
"Node already added",
self.nodes[0].addnode,
node=ip_port,
command='add')
# check that node can be removed
self.nodes[0].addnode(node=ip_port, command='remove')
assert_equal(self.nodes[0].getaddednodeinfo(), [])
# check that trying to remove the node again returns an error
assert_raises_rpc_error(-24,
"Node could not be removed",
self.nodes[0].addnode,
node=ip_port,
command='remove')
# 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):
self.log.info("Test getpeerinfo")
# Create a few getpeerinfo last_block/last_transaction/last_proof
# values.
if self.is_wallet_compiled():
self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1000000)
tip = self.nodes[1].generate(1)[0]
self.sync_all()
stake = create_coinbase_stakes(
self.nodes[1], [tip], self.nodes[1].get_deterministic_priv_key().key)
privkey = ECKey()
privkey.generate()
proof = self.nodes[1].buildavalancheproof(
42, 2000000000, bytes_to_wif(privkey.get_bytes()), stake)
self.nodes[1].sendavalancheproof(proof)
self.sync_proofs()
time_now = int(time.time())
peer_info = [x.getpeerinfo() for x in self.nodes]
# Verify last_block, last_transaction and last_proof keys/values.
for node, peer, field in product(range(self.num_nodes), range(2), [
'last_block', 'last_transaction', 'last_proof']):
assert field in peer_info[node][peer].keys()
if peer_info[node][peer][field] != 0:
assert_approx(peer_info[node][peer][field], time_now, vspan=60)
# 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("5.00"))
assert_equal(peer_info[1][0]['minfeefilter'], Decimal("10.00"))
# check the `servicesnames` field
for info in peer_info:
assert_net_servicesnames(int(info[0]["services"], 0x10),
info[0]["servicesnames"])
def test_service_flags(self):
self.log.info("Test service flags")
self.nodes[0].add_p2p_connection(
P2PInterface(), services=(
1 << 5) | (
1 << 63))
assert_equal(['UNKNOWN[2^5]', 'UNKNOWN[2^63]'],
self.nodes[0].getpeerinfo()[-1]['servicesnames'])
self.nodes[0].disconnect_p2ps()
def test_getnodeaddresses(self):
self.log.info("Test getnodeaddresses")
self.nodes[0].add_p2p_connection(P2PInterface())
# Add some addresses to the Address Manager over RPC. Due to the way
# bucket and bucket position are calculated, some of these addresses
# will collide.
imported_addrs = []
for i in range(10000):
first_octet = i >> 8
second_octet = i % 256
a = "{}.{}.1.1".format(first_octet, second_octet)
imported_addrs.append(a)
self.nodes[0].addpeeraddress(a, 8333)
# Obtain addresses via rpc call and check they were ones sent in before.
#
# Maximum possible addresses in addrman is 10000, although actual
# number will usually be less due to bucket and bucket position
# collisions.
node_addresses = self.nodes[0].getnodeaddresses(0)
assert_greater_than(len(node_addresses), 5000)
assert_greater_than(10000, len(node_addresses))
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)
node_addresses = self.nodes[0].getnodeaddresses(1)
assert_equal(len(node_addresses), 1)
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()