From 13927cf23a5c4c966dc05996496a73b002466c0a Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Tue, 18 Sep 2018 19:49:24 -0400 Subject: [PATCH] Merge #13152: [rpc] Add getnodeaddresses RPC command a2eb6f5405 [rpc] Add getnodeaddresses RPC command (chris-belcher) Pull request description: Implements issue https://github.com/bitcoin/bitcoin/issues/9463 New getnodeaddresses call gives access via RPC to the peers known by the node. It may be useful for bitcoin wallets to broadcast their transactions over tor for improved privacy without using the centralized DNS seeds. getnodeaddresses is very similar to the getaddr p2p method. Please advise me on the best approach for writing an automated test. By my reading the getaddr p2p method also isn't really tested. Tree-SHA512: ad03abf518847476495b76a2f5394b8030aa86654429167fa618e21460abb505c10ef9817ec1b80472320d41d0aff5dc94a8efce023aaaaf5e81386aa92b852b --- src/rpc/client.cpp | 1 + src/rpc/net.cpp | 53 ++++++++++++++++++++++++++++++++++++++ test/functional/rpc_net.py | 39 ++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c83846e78db06e..5c4006203c61f1 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -206,6 +206,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rescanblockchain", 1, "stop_height"}, { "createwallet", 1, "disable_private_keys"}, { "stop", 0, "wait" }, + { "getnodeaddresses", 0, "count"}, }; class CRPCConvertTable diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f1aea21ba4ec58..1eb95078e0b959 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -687,6 +687,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; +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -702,6 +754,7 @@ static const CRPCCommand commands[] = { "network", "listbanned", &listbanned, {} }, { "network", "clearbanned", &clearbanned, {} }, { "network", "setnetworkactive", &setnetworkactive, {"state"} }, + { "network", "getnodeaddresses", &getnodeaddresses, {"count"} }, }; void RegisterNetRPCCommands(CRPCTable &t) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index a3cab4d4bbc1ab..725eca1eed512c 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -11,11 +11,14 @@ 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): @@ -33,6 +36,7 @@ def run_test(self): 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 @@ -102,5 +106,40 @@ def _test_getpeerinfo(self): assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr']) assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) + 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()