Merge #6147: feat: aim to have at least 2 onion connections, guard them from eviction

e775b74d5e docs: add release notes for 6147 (pasta)
127a4d23a5 feat: aim to have 2 onion connections when possible, guard them from eviction (pasta)

Pull request description:

  ## Issue being fixed or feature implemented
  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 (in my experience)!

  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.

  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.

  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.

  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.

  ## What was done?
  We will always try to get 2 onion nodes (full or block only); and guard them from eviction

  ## How Has This Been Tested?
  Run a node; see over time that you start with 0 onion nodes, and over time you progress to having two of them!

  ## Breaking Changes
  None

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [ ] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

Top commit has no ACKs.

Tree-SHA512: cd15565751ae845302c71cac084ffba66340033d379ffa78d0aa6fa4ad8f65ddeccd55fa623dfaf7daeed5e38b5b2ec27683991275cf2b6edfbd8e114a1bfe60
This commit is contained in:
pasta 2024-07-31 13:21:54 -05:00
commit 84179f7e0e
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
5 changed files with 35 additions and 1 deletions

12
doc/release-notes-6147.md Normal file
View File

@ -0,0 +1,12 @@
P2P and Network Changes
-----------------------
### Improved Onion Connectivity
To enhance censorship resistance and mitigate network partitioning risks, Dash Core now aims to maintain at least two
outbound onion connections and generally protects these connections from eviction. As a result of the low percentage
of gossiped addresses being onion nodes, it was often the case where, unless you specify `onlynet=onion`, a node
would rarely if ever establish any outbound onion connections. This change ensures that nodes which can access the
onion network maintain a few onion connections. As a result, network messages could continue to propagate across
the network even if non-onion IPv4 traffic is blocked, reducing the risk of partitioning. Additionally, this update improves
security by enabling p2p encryption for these peers. (#6147)

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