Skip to content

Commit 18fe765

Browse files
committed
merge bitcoin#21528: Reduce addr blackholes
1 parent c1874c6 commit 18fe765

File tree

6 files changed

+147
-27
lines changed

6 files changed

+147
-27
lines changed

src/net.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,8 @@ class CNode
603603
};
604604

605605
// in bitcoin: m_tx_relay == nullptr if we're not relaying transactions with this peer
606-
// in dash: m_tx_relay should never be nullptr, use `!IsBlockOnlyConn() == false` instead
606+
// in dash: m_tx_relay should never be nullptr, we don't relay transactions if
607+
// `IsBlockOnlyConn() == true` is instead
607608
std::unique_ptr<TxRelay> m_tx_relay{std::make_unique<TxRelay>()};
608609

609610
/** UNIX epoch time of the last block received from this peer that we had

src/net_processing.cpp

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,31 @@ struct Peer {
264264

265265
/** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */
266266
std::vector<CAddress> m_addrs_to_send;
267-
/** Probabilistic filter of addresses that this peer already knows.
268-
* Used to avoid relaying addresses to this peer more than once. */
269-
const std::unique_ptr<CRollingBloomFilter> m_addr_known;
267+
/** Probabilistic filter to track recent addr messages relayed with this
268+
* peer. Used to avoid relaying redundant addresses to this peer.
269+
*
270+
* We initialize this filter for outbound peers (other than
271+
* block-relay-only connections) or when an inbound peer sends us an
272+
* address related message (ADDR, ADDRV2, GETADDR).
273+
*
274+
* Presence of this filter must correlate with m_addr_relay_enabled.
275+
**/
276+
std::unique_ptr<CRollingBloomFilter> m_addr_known;
277+
/** Whether we are participating in address relay with this connection.
278+
*
279+
* We set this bool to true for outbound peers (other than
280+
* block-relay-only connections), or when an inbound peer sends us an
281+
* address related message (ADDR, ADDRV2, GETADDR).
282+
*
283+
* We use this bool to decide whether a peer is eligible for gossiping
284+
* addr messages. This avoids relaying to peers that are unlikely to
285+
* forward them, effectively blackholing self announcements. Reasons
286+
* peers might support addr relay on the link include that they connected
287+
* to us as a block-relay-only peer or they are a light client.
288+
*
289+
* This field must correlate with whether m_addr_known has been
290+
* initialized.*/
291+
std::atomic_bool m_addr_relay_enabled{false};
270292
/** Whether a getaddr request to this peer is outstanding. */
271293
bool m_getaddr_sent{false};
272294
/** Guards address sending timers. */
@@ -298,9 +320,8 @@ struct Peer {
298320
/** Work queue of items requested by this peer **/
299321
std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
300322

301-
explicit Peer(NodeId id, bool addr_relay)
323+
explicit Peer(NodeId id)
302324
: m_id(id)
303-
, m_addr_known{addr_relay ? std::make_unique<CRollingBloomFilter>(5000, 0.001) : nullptr}
304325
{}
305326
};
306327

@@ -523,6 +544,14 @@ class PeerManagerImpl final : public PeerManager
523544
*/
524545
void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv);
525546

547+
/** Checks if address relay is permitted with peer. If needed, initializes
548+
* the m_addr_known bloom filter and sets m_addr_relay_enabled to true.
549+
*
550+
* @return True if address relay is enabled with peer
551+
* False if address relay is disallowed
552+
*/
553+
bool SetupAddressRelay(CNode& node, Peer& peer);
554+
526555
/** Number of nodes with fSyncStarted. */
527556
int nSyncStarted GUARDED_BY(cs_main) = 0;
528557

@@ -852,11 +881,6 @@ static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
852881
return &it->second;
853882
}
854883

855-
static bool RelayAddrsWithPeer(const Peer& peer)
856-
{
857-
return peer.m_addr_known != nullptr;
858-
}
859-
860884
/**
861885
* Whether the peer supports the address. For example, a peer that does not
862886
* implement BIP155 cannot receive Tor v3 addresses because it requires
@@ -1330,9 +1354,7 @@ void PeerManagerImpl::InitializeNode(CNode *pnode) {
13301354
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, pnode->IsInboundConn()));
13311355
}
13321356
{
1333-
// Addr relay is disabled for outbound block-relay-only peers to
1334-
// prevent adversaries from inferring these links from addr traffic.
1335-
PeerRef peer = std::make_shared<Peer>(nodeid, /* addr_relay = */ !pnode->IsBlockOnlyConn());
1357+
PeerRef peer = std::make_shared<Peer>(nodeid);
13361358
LOCK(m_peer_mutex);
13371359
m_peer_map.emplace_hint(m_peer_map.end(), nodeid, std::move(peer));
13381360
}
@@ -1465,6 +1487,7 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats)
14651487
stats.m_ping_wait = ping_wait;
14661488
stats.m_addr_processed = peer->m_addr_processed.load();
14671489
stats.m_addr_rate_limited = peer->m_addr_rate_limited.load();
1490+
stats.m_addr_relay_enabled = peer->m_addr_relay_enabled.load();
14681491

14691492
return true;
14701493
}
@@ -1654,7 +1677,7 @@ bool PeerManagerImpl::CanRelayAddrs(NodeId pnode) const
16541677
PeerRef peer = GetPeerRef(pnode);
16551678
if (peer == nullptr)
16561679
return false;
1657-
return RelayAddrsWithPeer(*peer);
1680+
return peer->m_addr_relay_enabled;
16581681
}
16591682

16601683
bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state,
@@ -2128,7 +2151,7 @@ void PeerManagerImpl::RelayAddress(NodeId originator,
21282151
LOCK(m_peer_mutex);
21292152

21302153
for (auto& [id, peer] : m_peer_map) {
2131-
if (RelayAddrsWithPeer(*peer) && id != originator && IsAddrCompatible(*peer, addr)) {
2154+
if (peer->m_addr_relay_enabled && id != originator && IsAddrCompatible(*peer, addr)) {
21322155
uint64_t hashKey = CSipHasher(hasher).Write(id).Finalize();
21332156
for (unsigned int i = 0; i < nRelayNodes; i++) {
21342157
if (hashKey > best[i].first) {
@@ -2354,7 +2377,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
23542377
}
23552378
++it;
23562379

2357-
if (!RelayAddrsWithPeer(peer) && NetMessageViolatesBlocksOnly(inv.GetCommand())) {
2380+
if (!peer.m_addr_relay_enabled && NetMessageViolatesBlocksOnly(inv.GetCommand())) {
23582381
// Note that if we receive a getdata for non-block messages
23592382
// from a block-relay-only outbound peer that violate the policy,
23602383
// we skip such getdata messages from this peer
@@ -3208,7 +3231,8 @@ void PeerManagerImpl::ProcessMessage(
32083231
UpdatePreferredDownload(pfrom, State(pfrom.GetId()));
32093232
}
32103233

3211-
if (!pfrom.IsInboundConn() && !pfrom.IsBlockOnlyConn()) {
3234+
// Self advertisement & GETADDR logic
3235+
if (!pfrom.IsInboundConn() && SetupAddressRelay(pfrom, *peer)) {
32123236
// For outbound peers, we try to relay our address (so that other
32133237
// nodes can try to find us more quickly, as we have no guarantee
32143238
// that an outbound peer is even aware of how to reach us) and do a
@@ -3217,8 +3241,9 @@ void PeerManagerImpl::ProcessMessage(
32173241
// empty and no one will know who we are, so these mechanisms are
32183242
// important to help us connect to the network.
32193243
//
3220-
// We skip this for block-relay-only peers to avoid potentially leaking
3221-
// information about our block-relay-only connections via address relay.
3244+
// We skip this for block-relay-only peers. We want to avoid
3245+
// potentially leaking addr information and we do not want to
3246+
// indicate to the peer that we will participate in addr relay.
32223247
if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
32233248
{
32243249
CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices());
@@ -3394,10 +3419,11 @@ void PeerManagerImpl::ProcessMessage(
33943419

33953420
s >> vAddr;
33963421

3397-
if (!RelayAddrsWithPeer(*peer)) {
3422+
if (!SetupAddressRelay(pfrom, *peer)) {
33983423
LogPrint(BCLog::NET, "ignoring %s message from %s peer=%d\n", msg_type, pfrom.ConnectionTypeAsString(), pfrom.GetId());
33993424
return;
34003425
}
3426+
34013427
if (vAddr.size() > MAX_ADDR_TO_SEND)
34023428
{
34033429
Misbehaving(pfrom.GetId(), 20, strprintf("%s message size = %u", msg_type, vAddr.size()));
@@ -4371,6 +4397,8 @@ void PeerManagerImpl::ProcessMessage(
43714397
return;
43724398
}
43734399

4400+
SetupAddressRelay(pfrom, *peer);
4401+
43744402
// Only send one GetAddr response per connection to reduce resource waste
43754403
// and discourage addr stamping of INV announcements.
43764404
if (peer->m_getaddr_recvd) {
@@ -5025,7 +5053,7 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::mic
50255053
void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time)
50265054
{
50275055
// Nothing to do for non-address-relay peers
5028-
if (!RelayAddrsWithPeer(peer)) return;
5056+
if (!peer.m_addr_relay_enabled) return;
50295057

50305058
LOCK(peer.m_addr_send_times_mutex);
50315059
// Periodically advertise our local address to the peer.
@@ -5108,6 +5136,22 @@ class CompareInvMempoolOrder
51085136
};
51095137
}
51105138

5139+
bool PeerManagerImpl::SetupAddressRelay(CNode& node, Peer& peer)
5140+
{
5141+
// We don't participate in addr relay with outbound block-relay-only
5142+
// connections to prevent providing adversaries with the additional
5143+
// information of addr traffic to infer the link.
5144+
if (node.IsBlockOnlyConn()) return false;
5145+
5146+
if (!peer.m_addr_relay_enabled.exchange(true)) {
5147+
// First addr message we have received from the peer, initialize
5148+
// m_addr_known
5149+
peer.m_addr_known = std::make_unique<CRollingBloomFilter>(5000, 0.001);
5150+
}
5151+
5152+
return true;
5153+
}
5154+
51115155
bool PeerManagerImpl::SendMessages(CNode* pto)
51125156
{
51135157
assert(m_llmq_ctx);

src/net_processing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct CNodeStateStats {
4343
std::vector<int> vHeightInFlight;
4444
uint64_t m_addr_processed = 0;
4545
uint64_t m_addr_rate_limited = 0;
46+
bool m_addr_relay_enabled{false};
4647
};
4748

4849
class PeerManager : public CValidationInterface, public NetEventsInterface

src/rpc/net.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ static RPCHelpMan getpeerinfo()
104104
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
105105
{RPCResult::Type::STR, "addrbind", "(ip:port) Bind address of the connection to the peer"},
106106
{RPCResult::Type::STR, "addrlocal", "(ip:port) Local address as reported by the peer"},
107+
{RPCResult::Type::BOOL, "addr_relay_enabled", "Whether we participate in address relay with this peer"},
107108
{RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
108109
{RPCResult::Type::STR, "mapped_as", "The AS in the BGP route to the peer used for diversifying peer selection"},
109110
{RPCResult::Type::STR_HEX, "services", "The services offered"},
@@ -192,6 +193,7 @@ static RPCHelpMan getpeerinfo()
192193
if (!(stats.addrLocal.empty())) {
193194
obj.pushKV("addrlocal", stats.addrLocal);
194195
}
196+
obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
195197
obj.pushKV("network", GetNetworkName(stats.m_network));
196198
if (stats.m_mapped_as != 0) {
197199
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));

test/functional/p2p_addr_relay.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,28 @@
1010
CAddress,
1111
NODE_NETWORK,
1212
msg_addr,
13-
msg_getaddr
13+
msg_getaddr,
14+
msg_verack
1415
)
1516
from test_framework.p2p import (
1617
P2PInterface,
1718
p2p_lock,
1819
)
1920
from test_framework.test_framework import BitcoinTestFramework
20-
from test_framework.util import assert_equal
21+
from test_framework.util import assert_equal, assert_greater_than
2122
import random
2223

2324

2425
class AddrReceiver(P2PInterface):
2526
num_ipv4_received = 0
2627
test_addr_contents = False
2728
_tokens = 1
29+
send_getaddr = True
2830

29-
def __init__(self, test_addr_contents=False):
31+
def __init__(self, test_addr_contents=False, send_getaddr=True):
3032
super().__init__()
3133
self.test_addr_contents = test_addr_contents
34+
self.send_getaddr = send_getaddr
3235

3336
def on_addr(self, message):
3437
for addr in message.addrs:
@@ -58,6 +61,11 @@ def increment_tokens(self, n):
5861
def addr_received(self):
5962
return self.num_ipv4_received != 0
6063

64+
def on_version(self, message):
65+
self.send_message(msg_verack())
66+
if (self.send_getaddr):
67+
self.send_message(msg_getaddr())
68+
6169
def getaddr_received(self):
6270
return self.message_count['getaddr'] > 0
6371

@@ -72,6 +80,10 @@ def set_test_params(self):
7280
def run_test(self):
7381
self.oversized_addr_test()
7482
self.relay_tests()
83+
self.inbound_blackhole_tests()
84+
85+
# This test populates the addrman, which can impact the node's behavior
86+
# in subsequent tests
7587
self.getaddr_tests()
7688
self.blocksonly_mode_tests()
7789
self.rate_limit_tests()
@@ -152,7 +164,7 @@ def relay_tests(self):
152164
self.nodes[0].disconnect_p2ps()
153165

154166
self.log.info('Check relay of addresses received from outbound peers')
155-
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True))
167+
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True, send_getaddr=False))
156168
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay")
157169
msg = self.setup_addr_msg(2)
158170
self.send_addr_msg(full_outbound_peer, msg, [inbound_peer])
@@ -163,6 +175,9 @@ def relay_tests(self):
163175
# of the outbound peer which is often sent before the GETADDR response.
164176
assert_equal(inbound_peer.num_ipv4_received, 0)
165177

178+
# Send an empty ADDR message to intialize address relay on this connection.
179+
inbound_peer.send_and_ping(msg_addr())
180+
166181
self.log.info('Check that subsequent addr messages sent from an outbound peer are relayed')
167182
msg2 = self.setup_addr_msg(2)
168183
self.send_addr_msg(full_outbound_peer, msg2, [inbound_peer])
@@ -180,7 +195,63 @@ def relay_tests(self):
180195

181196
self.nodes[0].disconnect_p2ps()
182197

198+
def sum_addr_messages(self, msgs_dict):
199+
return sum(bytes_received for (msg, bytes_received) in msgs_dict.items() if msg in ['addr', 'addrv2', 'getaddr'])
200+
201+
def inbound_blackhole_tests(self):
202+
self.log.info('Check that we only relay addresses to inbound peers who have previously sent us addr related messages')
203+
204+
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
205+
receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
206+
blackhole_peer = self.nodes[0].add_p2p_connection(AddrReceiver(send_getaddr=False))
207+
initial_addrs_received = receiver_peer.num_ipv4_received
208+
209+
peerinfo = self.nodes[0].getpeerinfo()
210+
assert_equal(peerinfo[0]['addr_relay_enabled'], True) # addr_source
211+
assert_equal(peerinfo[1]['addr_relay_enabled'], True) # receiver_peer
212+
assert_equal(peerinfo[2]['addr_relay_enabled'], False) # blackhole_peer
213+
214+
# addr_source sends 2 addresses to node0
215+
msg = self.setup_addr_msg(2)
216+
addr_source.send_and_ping(msg)
217+
self.bump_mocktime(30 * 60)
218+
receiver_peer.sync_with_ping()
219+
blackhole_peer.sync_with_ping()
220+
221+
peerinfo = self.nodes[0].getpeerinfo()
222+
223+
# Confirm node received addr-related messages from receiver peer
224+
assert_greater_than(self.sum_addr_messages(peerinfo[1]['bytesrecv_per_msg']), 0)
225+
# And that peer received addresses
226+
assert_equal(receiver_peer.num_ipv4_received - initial_addrs_received, 2)
227+
228+
# Confirm node has not received addr-related messages from blackhole peer
229+
assert_equal(self.sum_addr_messages(peerinfo[2]['bytesrecv_per_msg']), 0)
230+
# And that peer did not receive addresses
231+
assert_equal(blackhole_peer.num_ipv4_received, 0)
232+
233+
self.log.info("After blackhole peer sends addr message, it becomes eligible for addr gossip")
234+
blackhole_peer.send_and_ping(msg_addr())
235+
236+
# Confirm node has now received addr-related messages from blackhole peer
237+
assert_greater_than(self.sum_addr_messages(peerinfo[1]['bytesrecv_per_msg']), 0)
238+
assert_equal(self.nodes[0].getpeerinfo()[2]['addr_relay_enabled'], True)
239+
240+
msg = self.setup_addr_msg(2)
241+
self.send_addr_msg(addr_source, msg, [receiver_peer, blackhole_peer])
242+
243+
# And that peer received addresses
244+
assert_equal(blackhole_peer.num_ipv4_received, 2)
245+
246+
self.nodes[0].disconnect_p2ps()
247+
183248
def getaddr_tests(self):
249+
# In the previous tests, the node answered GETADDR requests with an
250+
# empty addrman. Due to GETADDR response caching (see
251+
# CConnman::GetAddresses), the node would continue to provide 0 addrs
252+
# in response until enough time has passed or the node is restarted.
253+
self.restart_node(0)
254+
184255
self.log.info('Test getaddr behavior')
185256
self.log.info('Check that we send a getaddr message upon connecting to an outbound-full-relay peer')
186257
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay")
@@ -193,7 +264,7 @@ def getaddr_tests(self):
193264
assert_equal(block_relay_peer.getaddr_received(), False)
194265

195266
self.log.info('Check that we answer getaddr messages only from inbound peers')
196-
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
267+
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(send_getaddr=False))
197268
inbound_peer.sync_with_ping()
198269

199270
# Add some addresses to addrman

test/functional/test_framework/p2p.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ def on_version(self, message):
474474
self.send_message(msg_sendaddrv2())
475475
self.send_message(msg_verack())
476476
self.nServices = message.nServices
477+
self.send_message(msg_getaddr())
477478

478479
# Connection helper methods
479480

0 commit comments

Comments
 (0)