diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 8ac08cc49..0ef08d210 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -1,1066 +1,1067 @@ // 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 has not been 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"}, {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"}, {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("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", "\nAdd the address of a potential peer to the address manager. This " "RPC is for testing only.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address of the peer"}, {"port", RPCArg::Type::NUM, RPCArg::Optional::NO, "The port of the peer"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "success", "whether the peer address was successfully added to the " "address manager"}, }, }, RPCExamples{HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 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 (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 ebd1641db..0a1725b32 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -1,251 +1,266 @@ #!/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, wait_until, ) 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() 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'].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) 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 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) # 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, privkey.get_pubkey().get_bytes().hex(), 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()