feat: aim to have 2 onion connections when possible, guard them from eviction

In the past I've noticed that even when using `-proxy` over tor, I wouldn't actually gain any onion connections over time. This is even worse when using -onion. Sure it may expose an onion service, but you wouldn't gain any onion connections!

The goal here is to minimize easy-ish censorship and improve network-wide resistance to partitioning. It is not unimaginable that port 9999 could be blocked at large scale. This could potentially result in severe partitioning, and subsequent issues. In an attempt to avoid this, we should always try to have at least 2 outbound onion connections when at all possible. Hopefully this also makes onion addresses gossip better.

I don't think there is any real downside to this patch, stuff like masternode / quorum connections will still always happen over ipv4, but with this, blocks and transactions would continue to propogate across the network even if (non-onion) ipv4 traffic was all dropped.

This also adds a benefit of p2p encryption for these peers. As a result, there is improved plausible deniability that you produced a transaction, as it is possible you received it over onion and simply rebroadcast it over ipv4.

Arguably, it's not **ideal** to send so much traffic over tor, but hopefully as latency is higher, we will generally receive messages over ipv4 first and therefor not request them over the onion connections.
This commit is contained in:
pasta 2024-07-23 21:07:27 -05:00
parent 7ebd7785d6
commit 127a4d23a5
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
4 changed files with 23 additions and 1 deletions

View File

@ -2341,6 +2341,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
connOptions.nMaxConnections = nMaxConnections; connOptions.nMaxConnections = nMaxConnections;
connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay);
connOptions.m_max_outbound_onion = std::min(MAX_DESIRED_ONION_CONNECTIONS, connOptions.nMaxConnections / 2);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS; connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS;
connOptions.uiInterface = &uiInterface; connOptions.uiInterface = &uiInterface;

View File

@ -2573,12 +2573,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, CDe
// This is only done for mainnet and testnet // This is only done for mainnet and testnet
int nOutboundFullRelay = 0; int nOutboundFullRelay = 0;
int nOutboundBlockRelay = 0; int nOutboundBlockRelay = 0;
int nOutboundOnionRelay = 0;
std::set<std::vector<unsigned char> > setConnected; std::set<std::vector<unsigned char> > setConnected;
if (!Params().AllowMultipleAddressesFromGroup()) { if (!Params().AllowMultipleAddressesFromGroup()) {
LOCK(m_nodes_mutex); LOCK(m_nodes_mutex);
for (const CNode* pnode : m_nodes) { for (const CNode* pnode : m_nodes) {
if (pnode->IsFullOutboundConn() && !pnode->m_masternode_connection) nOutboundFullRelay++; if (pnode->IsFullOutboundConn() && !pnode->m_masternode_connection) nOutboundFullRelay++;
if (pnode->IsBlockOnlyConn()) nOutboundBlockRelay++; if (pnode->IsBlockOnlyConn()) nOutboundBlockRelay++;
if (pnode->IsFullOutboundConn() && pnode->ConnectedThroughNetwork() == Network::NET_ONION) nOutboundOnionRelay++;
// Netgroups for inbound and manual peers are not excluded because our goal here // Netgroups for inbound and manual peers are not excluded because our goal here
// is to not use multiple of our limited outbound slots on a single netgroup // is to not use multiple of our limited outbound slots on a single netgroup
@ -2613,6 +2615,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, CDe
auto now = GetTime<std::chrono::microseconds>(); auto now = GetTime<std::chrono::microseconds>();
bool anchor = false; bool anchor = false;
bool fFeeler = false; bool fFeeler = false;
bool onion_only = false;
// Determine what type of connection to open. Opening // Determine what type of connection to open. Opening
// BLOCK_RELAY connections to addresses from anchors.dat gets the highest // BLOCK_RELAY connections to addresses from anchors.dat gets the highest
@ -2662,6 +2665,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, CDe
next_feeler = PoissonNextSend(now, FEELER_INTERVAL); next_feeler = PoissonNextSend(now, FEELER_INTERVAL);
conn_type = ConnectionType::FEELER; conn_type = ConnectionType::FEELER;
fFeeler = true; fFeeler = true;
} else if (nOutboundOnionRelay < m_max_outbound_onion && IsReachable(Network::NET_ONION)) {
onion_only = true;
} else { } else {
// skip to next iteration of while loop // skip to next iteration of while loop
continue; continue;
@ -2744,6 +2749,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, CDe
if (!IsReachable(addr)) if (!IsReachable(addr))
continue; continue;
if (onion_only && !addr.IsTor()) continue;
// only consider very recently tried nodes after 30 failed attempts // only consider very recently tried nodes after 30 failed attempts
if (nANow - addr_last_try < 600 && nTries < 30) if (nANow - addr_last_try < 600 && nTries < 30)
@ -3951,6 +3957,10 @@ size_t CConnman::GetMaxOutboundNodeCount()
{ {
return m_max_outbound; return m_max_outbound;
} }
size_t CConnman::GetMaxOutboundOnionNodeCount()
{
return m_max_outbound_onion;
}
void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const
{ {

View File

@ -85,6 +85,8 @@ static const int MAX_ADDNODE_CONNECTIONS = 8;
static const auto INBOUND_EVICTION_PROTECTION_TIME{1s}; static const auto INBOUND_EVICTION_PROTECTION_TIME{1s};
/** Maximum number of block-relay-only outgoing connections */ /** Maximum number of block-relay-only outgoing connections */
static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2; static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2;
/** Maximum number of onion connections we will try harder to connect to / protect from eviction */
static const int MAX_DESIRED_ONION_CONNECTIONS = 2;
/** Maximum number of feeler connections */ /** Maximum number of feeler connections */
static const int MAX_FEELER_CONNECTIONS = 1; static const int MAX_FEELER_CONNECTIONS = 1;
/** -listen default */ /** -listen default */
@ -873,6 +875,7 @@ public:
int nMaxConnections = 0; int nMaxConnections = 0;
int m_max_outbound_full_relay = 0; int m_max_outbound_full_relay = 0;
int m_max_outbound_block_relay = 0; int m_max_outbound_block_relay = 0;
int m_max_outbound_onion = 0;
int nMaxAddnode = 0; int nMaxAddnode = 0;
int nMaxFeeler = 0; int nMaxFeeler = 0;
CClientUIInterface* uiInterface = nullptr; CClientUIInterface* uiInterface = nullptr;
@ -905,6 +908,7 @@ public:
nMaxConnections = connOptions.nMaxConnections; nMaxConnections = connOptions.nMaxConnections;
m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections);
m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay;
m_max_outbound_onion = connOptions.m_max_outbound_onion;
m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing;
nMaxAddnode = connOptions.nMaxAddnode; nMaxAddnode = connOptions.nMaxAddnode;
nMaxFeeler = connOptions.nMaxFeeler; nMaxFeeler = connOptions.nMaxFeeler;
@ -1179,6 +1183,7 @@ public:
size_t GetNodeCount(ConnectionDirection) const; size_t GetNodeCount(ConnectionDirection) const;
size_t GetMaxOutboundNodeCount(); size_t GetMaxOutboundNodeCount();
size_t GetMaxOutboundOnionNodeCount();
void GetNodeStats(std::vector<CNodeStats>& vstats) const; void GetNodeStats(std::vector<CNodeStats>& vstats) const;
bool DisconnectNode(const std::string& node); bool DisconnectNode(const std::string& node);
bool DisconnectNode(const CSubNet& subnet); bool DisconnectNode(const CSubNet& subnet);
@ -1515,6 +1520,9 @@ private:
// We do not relay tx or addr messages with these peers // We do not relay tx or addr messages with these peers
int m_max_outbound_block_relay; int m_max_outbound_block_relay;
// How many onion outbound peers we want; don't care if full or block only; does not increase m_max_outbound
int m_max_outbound_onion;
int nMaxAddnode; int nMaxAddnode;
int nMaxFeeler; int nMaxFeeler;
int m_max_outbound; int m_max_outbound;

View File

@ -5179,9 +5179,12 @@ void PeerManagerImpl::EvictExtraOutboundPeers(std::chrono::seconds now)
NodeId worst_peer = -1; NodeId worst_peer = -1;
int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max(); int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max();
// We want to prevent recently connected to Onion peers from being disconnected here, protect them as long as
// there are more non_onion nodes than onion nodes so far
size_t onion_count = 0;
m_connman.ForEachNode([&](CNode* pnode) { m_connman.ForEachNode([&](CNode* pnode) {
LockAssertion lock(::cs_main); LockAssertion lock(::cs_main);
if (pnode->addr.IsTor() && ++onion_count <= m_connman.GetMaxOutboundOnionNodeCount()) return;
// Don't disconnect masternodes just because they were slow in block announcement // Don't disconnect masternodes just because they were slow in block announcement
if (pnode->m_masternode_connection) return; if (pnode->m_masternode_connection) return;
// Only consider outbound-full-relay peers that are not already // Only consider outbound-full-relay peers that are not already