diff options
author | chris-belcher <chris-belcher@users.noreply.github.com> | 2018-05-02 13:19:40 +0100 |
---|---|---|
committer | chris-belcher <chris-belcher@users.noreply.github.com> | 2018-09-17 22:55:23 +0100 |
commit | a2eb6f540538d32725aecf678301a96247db6fd9 (patch) | |
tree | b0932b58f76526fa61d9e43a7fc6935e16d3d836 | |
parent | 4901c00792c1dabae4bb01e6373c9b1ed9ef3008 (diff) |
[rpc] Add getnodeaddresses RPC command
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.
Tests the new rpc call by feeding IP address to a test node via the p2p
protocol, then obtaining someone of those addresses with
getnodeaddresses and checking that they are a subset.
-rw-r--r-- | doc/release-notes-13152.md | 4 | ||||
-rw-r--r-- | src/rpc/client.cpp | 1 | ||||
-rw-r--r-- | src/rpc/net.cpp | 53 | ||||
-rwxr-xr-x | test/functional/rpc_net.py | 39 |
4 files changed, 97 insertions, 0 deletions
diff --git a/doc/release-notes-13152.md b/doc/release-notes-13152.md new file mode 100644 index 0000000000..72526f5355 --- /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 f4d44fc809..649e222c39 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 099a7cf1da..846d90cd0a 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<CAddress> vAddr = g_connman->GetAddresses(); + UniValue ret(UniValue::VARR); + + int address_return_count = std::min<int>(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 a46518200c..1e525214fa 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() |