diff --git a/doc/release-notes-13152.md b/doc/release-notes-13152.md new file mode 100644 index 000000000..72526f535 --- /dev/null +++ b/doc/release-notes-13152.md @@ -0,0 +1,4 @@ +New RPC methods +------------ + +- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds. \ No newline at end of file diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index f4d44fc80..649e222c3 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -163,6 +163,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rescanblockchain", 0, "start_height"}, { "rescanblockchain", 1, "stop_height"}, { "createwallet", 1, "disable_private_keys"}, + { "getnodeaddresses", 0, "count"}, }; // clang-format on diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 099a7cf1d..846d90cd0 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -626,6 +626,58 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) return g_connman->GetNetworkActive(); } +static UniValue getnodeaddresses(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() > 1) { + throw std::runtime_error( + "getnodeaddresses ( count )\n" + "\nReturn known addresses which can potentially be used to find new nodes in the network\n" + "\nArguments:\n" + "1. \"count\" (numeric, optional) How many addresses to return. Limited to the smaller of " + std::to_string(ADDRMAN_GETADDR_MAX) + + " or " + std::to_string(ADDRMAN_GETADDR_MAX_PCT) + "% of all known addresses. (default = 1)\n" + "\nResult:\n" + "[\n" + " {\n" + " \"time\": ttt, (numeric) Timestamp in seconds since epoch (Jan 1 1970 GMT) keeping track of when the node was last seen\n" + " \"services\": n, (numeric) The services offered\n" + " \"address\": \"host\", (string) The address of the node\n" + " \"port\": n (numeric) The port of the node\n" + " }\n" + " ,....\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getnodeaddresses", "8") + + HelpExampleRpc("getnodeaddresses", "8") + ); + } + if (!g_connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + + int count = 1; + if (!request.params[0].isNull()) { + count = request.params[0].get_int(); + if (count <= 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range"); + } + } + // returns a shuffled list of CAddress + std::vector vAddr = g_connman->GetAddresses(); + UniValue ret(UniValue::VARR); + + int address_return_count = std::min(count, vAddr.size()); + for (int i = 0; i < address_return_count; ++i) { + UniValue obj(UniValue::VOBJ); + const CAddress& addr = vAddr[i]; + obj.pushKV("time", (int)addr.nTime); + obj.pushKV("services", (uint64_t)addr.nServices); + obj.pushKV("address", addr.ToStringIP()); + obj.pushKV("port", addr.GetPort()); + ret.push_back(obj); + } + return ret; +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -642,6 +694,7 @@ static const CRPCCommand commands[] = { "network", "listbanned", &listbanned, {} }, { "network", "clearbanned", &clearbanned, {} }, { "network", "setnetworkactive", &setnetworkactive, {"state"} }, + { "network", "getnodeaddresses", &getnodeaddresses, {"count"} }, }; // clang-format on diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index a46518200..1e525214f 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -13,11 +13,14 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than_or_equal, + assert_greater_than, assert_raises_rpc_error, connect_nodes_bi, p2p_port, wait_until, ) +from test_framework.mininode import P2PInterface +from test_framework.messages import CAddress, msg_addr, NODE_NETWORK, NODE_WITNESS class NetTest(BitcoinTestFramework): def set_test_params(self): @@ -31,6 +34,7 @@ class NetTest(BitcoinTestFramework): self._test_getnetworkinginfo() self._test_getaddednodeinfo() self._test_getpeerinfo() + self._test_getnodeaddresses() def _test_connection_count(self): # connect_nodes_bi connects each node to the other @@ -101,5 +105,40 @@ class NetTest(BitcoinTestFramework): assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500")) assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000")) + def _test_getnodeaddresses(self): + self.nodes[0].add_p2p_connection(P2PInterface()) + + # send some addresses to the node via the p2p message addr + msg = msg_addr() + imported_addrs = [] + for i in range(256): + a = "123.123.123.{}".format(i) + imported_addrs.append(a) + addr = CAddress() + addr.time = 100000000 + addr.nServices = NODE_NETWORK | NODE_WITNESS + addr.ip = a + addr.port = 8333 + msg.addrs.append(addr) + self.nodes[0].p2p.send_and_ping(msg) + + # obtain addresses via rpc call and check they were ones sent in before + REQUEST_COUNT = 10 + node_addresses = self.nodes[0].getnodeaddresses(REQUEST_COUNT) + assert_equal(len(node_addresses), REQUEST_COUNT) + for a in node_addresses: + assert_greater_than(a["time"], 1527811200) # 1st June 2018 + assert_equal(a["services"], NODE_NETWORK | NODE_WITNESS) + assert a["address"] in imported_addrs + assert_equal(a["port"], 8333) + + assert_raises_rpc_error(-8, "Address count out of range", self.nodes[0].getnodeaddresses, -1) + + # addrman's size cannot be known reliably after insertion, as hash collisions may occur + # so only test that requesting a large number of addresses returns less than that + LARGE_REQUEST_COUNT = 10000 + node_addresses = self.nodes[0].getnodeaddresses(LARGE_REQUEST_COUNT) + assert_greater_than(LARGE_REQUEST_COUNT, len(node_addresses)) + if __name__ == '__main__': NetTest().main()