[rpc/net] Introduce addconnection to test outbounds & blockrelay

Add a new RPC endpoint to enable opening outbound connections from
the tests. The functional test framework currently uses the addnode RPC, which
has different behavior than general outbound peers. These changes enable
creating both full-relay and block-relay-only connections. The new RPC
endpoint calls through to a newly introduced AddConnection method on
CConnman that ensures we stay within the allocated max.
This commit is contained in:
Amiti Uttarwar
2020-06-02 09:46:41 -07:00
parent b6a71b80d2
commit 5bc04e8837
4 changed files with 93 additions and 0 deletions

View File

@ -1132,6 +1132,27 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
RandAddEvent((uint32_t)id);
}
bool CConnman::AddConnection(const std::string& address, ConnectionType conn_type)
{
if (conn_type != ConnectionType::OUTBOUND_FULL_RELAY && conn_type != ConnectionType::BLOCK_RELAY) return false;
const int max_connections = conn_type == ConnectionType::OUTBOUND_FULL_RELAY ? m_max_outbound_full_relay : m_max_outbound_block_relay;
// Count existing connections
int existing_connections = WITH_LOCK(cs_vNodes,
return std::count_if(vNodes.begin(), vNodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; }););
// Max connections of specified type already exist
if (existing_connections >= max_connections) return false;
// Max total outbound connections already exist
CSemaphoreGrant grant(*semOutbound, true);
if (!grant) return false;
OpenNetworkConnection(CAddress(), false, &grant, address.c_str(), conn_type);
return true;
}
void CConnman::DisconnectNodes()
{
{

View File

@ -955,6 +955,19 @@ public:
bool RemoveAddedNode(const std::string& node);
std::vector<AddedNodeInfo> GetAddedNodeInfo();
/**
* Attempts to open a connection. Currently only used from tests.
*
* @param[in] address Address of node to try connecting to
* @param[in] conn_type ConnectionType::OUTBOUND or ConnectionType::BLOCK_RELAY
* @return bool Returns false if there are no available
* slots for this connection:
* - conn_type not a supported ConnectionType
* - Max total outbound connection capacity filled
* - Max connection capacity for type is filled
*/
bool AddConnection(const std::string& address, ConnectionType conn_type);
size_t GetNodeCount(NumConnections num);
void GetNodeStats(std::vector<CNodeStats>& vstats);
bool DisconnectNode(const std::string& node);

View File

@ -5,6 +5,7 @@
#include <rpc/server.h>
#include <banman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <core_io.h>
#include <net.h>
@ -314,6 +315,61 @@ static RPCHelpMan addnode()
};
}
static RPCHelpMan addconnection()
{
return RPCHelpMan{"addconnection",
"\nOpen an outbound connection to a specified node. This RPC is for testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address and port to attempt connecting to."},
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open, either \"outbound-full-relay\" or \"block-relay-only\"."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{ RPCResult::Type::STR, "address", "Address of newly added connection." },
{ RPCResult::Type::STR, "connection_type", "Type of connection opened." },
}},
RPCExamples{
HelpExampleCli("addconnection", "\"192.168.0.6:8333\" \"outbound-full-relay\"")
+ HelpExampleRpc("addconnection", "\"192.168.0.6:8333\" \"outbound-full-relay\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
if (Params().NetworkIDString() != CBaseChainParams::REGTEST) {
throw std::runtime_error("addconnection is for regression testing (-regtest mode) only.");
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR});
const std::string address = request.params[0].get_str();
const std::string conn_type_in{TrimString(request.params[1].get_str())};
ConnectionType conn_type{};
if (conn_type_in == "outbound-full-relay") {
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
} else if (conn_type_in == "block-relay-only") {
conn_type = ConnectionType::BLOCK_RELAY;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
}
NodeContext& node = EnsureNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled.");
}
const bool success = node.connman->AddConnection(address, conn_type);
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_CAPACITY_REACHED, "Error: Already at capacity for specified connection type.");
}
UniValue info(UniValue::VOBJ);
info.pushKV("address", address);
info.pushKV("connection_type", conn_type_in);
return info;
},
};
}
static RPCHelpMan disconnectnode()
{
return RPCHelpMan{"disconnectnode",
@ -900,6 +956,8 @@ static const CRPCCommand commands[] =
{ "network", "clearbanned", &clearbanned, {} },
{ "network", "setnetworkactive", &setnetworkactive, {"state"} },
{ "network", "getnodeaddresses", &getnodeaddresses, {"count"} },
{ "hidden", "addconnection", &addconnection, {"address", "connection_type"} },
{ "hidden", "addpeeraddress", &addpeeraddress, {"address", "port"} },
};
// clang-format on

View File

@ -62,6 +62,7 @@ enum RPCErrorCode
RPC_CLIENT_NODE_NOT_CONNECTED = -29, //!< Node to disconnect not found in connected nodes
RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //!< Invalid IP/Subnet
RPC_CLIENT_P2P_DISABLED = -31, //!< No valid connection manager instance found
RPC_CLIENT_NODE_CAPACITY_REACHED= -34, //!< Max number of outbound or block-relay connections already open
//! Chain errors
RPC_CLIENT_MEMPOOL_DISABLED = -33, //!< No mempool instance found