Skip to content

Commit f0a834e

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#18642: Use std::chrono for the time to rotate destination of addr messages + tests
2ff8f4d Add tests for addr destination rotation (Gleb Naumenko) 77ccb7f Use std::chrono for salting when randomizing ADDR destination (Gleb Naumenko) Pull request description: We currently assign a destination peer for relaying particular addresses of nodes every 24 hours, and then rotate. This is done for rate-limiting (ultimately for privacy leak reduction I think?). Before this change, 24 hours was defined as uint. I replaced it with std::chrono, which is mockable and type-safe. Also added couple tests for this behavior. ACKs for top commit: jonatack: ACK 2ff8f4d Tree-SHA512: 16f703ef3ffee13ce3afa82ca7b4baa27308af18cd2eececdce5565badfb68656a2ad9c4594b73772e4bfa99b3fb15f8e4089c1cb4be98c0bae6730a9d2f8a25
2 parents 132d5f8 + 2ff8f4d commit f0a834e

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

src/net_processing.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288;
135135
static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24h};
136136
/** Average delay between peer address broadcasts */
137137
static constexpr auto AVG_ADDRESS_BROADCAST_INTERVAL{30s};
138+
/** Delay between rotating the peers we relay a particular address to */
139+
static constexpr auto ROTATE_ADDR_RELAY_DEST_INTERVAL{24h};
138140
/** Average delay between trickled inventory transmissions for inbound peers.
139141
* Blocks and peers with NetPermissionFlags::NoBan permission bypass this. */
140142
static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL{5s};
@@ -1818,9 +1820,12 @@ void PeerManagerImpl::RelayAddress(NodeId originator,
18181820
// Use deterministic randomness to send to the same nodes for 24 hours
18191821
// at a time so the m_addr_knowns of the chosen nodes prevent repeats
18201822
const uint64_t hash_addr{CServiceHash(0, 0)(addr)};
1823+
const auto current_time{GetTime<std::chrono::seconds>()};
1824+
// Adding address hash makes exact rotation time different per address, while preserving periodicity.
1825+
const uint64_t time_addr{(static_cast<uint64_t>(count_seconds(current_time)) + hash_addr) / count_seconds(ROTATE_ADDR_RELAY_DEST_INTERVAL)};
18211826
const CSipHasher hasher{m_connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY)
18221827
.Write(hash_addr)
1823-
.Write((GetTime() + hash_addr) / (24 * 60 * 60))};
1828+
.Write(time_addr)};
18241829
FastRandomContext insecure_rand;
18251830

18261831
// Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers.

test/functional/p2p_addr_relay.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,19 @@
2121
P2P_SERVICES,
2222
)
2323
from test_framework.test_framework import BitcoinTestFramework
24-
from test_framework.util import assert_equal, assert_greater_than
24+
from test_framework.util import (
25+
assert_equal,
26+
assert_greater_than,
27+
assert_greater_than_or_equal
28+
)
29+
30+
ONE_MINUTE = 60
31+
TEN_MINUTES = 10 * ONE_MINUTE
32+
ONE_HOUR = 60 * ONE_MINUTE
33+
TWO_HOURS = 2 * ONE_HOUR
34+
ONE_DAY = 24 * ONE_HOUR
2535

36+
ADDR_DESTINATIONS_THRESHOLD = 4
2637

2738
class AddrReceiver(P2PInterface):
2839
num_ipv4_received = 0
@@ -85,6 +96,9 @@ def run_test(self):
8596
self.relay_tests()
8697
self.inbound_blackhole_tests()
8798

99+
self.destination_rotates_once_in_24_hours_test()
100+
self.destination_rotates_more_than_once_over_several_days_test()
101+
88102
# This test populates the addrman, which can impact the node's behavior
89103
# in subsequent tests
90104
self.getaddr_tests()
@@ -362,6 +376,56 @@ def rate_limit_tests(self):
362376

363377
self.nodes[0].disconnect_p2ps()
364378

379+
def get_nodes_that_received_addr(self, peer, receiver_peer, addr_receivers,
380+
time_interval_1, time_interval_2):
381+
382+
# Clean addr response related to the initial getaddr. There is no way to avoid initial
383+
# getaddr because the peer won't self-announce then.
384+
for addr_receiver in addr_receivers:
385+
addr_receiver.num_ipv4_received = 0
386+
387+
for _ in range(10):
388+
self.mocktime += time_interval_1
389+
self.msg.addrs[0].time = self.mocktime + TEN_MINUTES
390+
self.nodes[0].setmocktime(self.mocktime)
391+
with self.nodes[0].assert_debug_log(['received: addr (31 bytes) peer=0']):
392+
peer.send_and_ping(self.msg)
393+
self.mocktime += time_interval_2
394+
self.nodes[0].setmocktime(self.mocktime)
395+
receiver_peer.sync_with_ping()
396+
return [node for node in addr_receivers if node.addr_received()]
397+
398+
def destination_rotates_once_in_24_hours_test(self):
399+
self.restart_node(0, [])
400+
401+
self.log.info('Test within 24 hours an addr relay destination is rotated at most once')
402+
self.mocktime = int(time.time())
403+
self.msg = self.setup_addr_msg(1)
404+
self.addr_receivers = []
405+
peer = self.nodes[0].add_p2p_connection(P2PInterface())
406+
receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
407+
addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)]
408+
nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, 0, TWO_HOURS) # 10 intervals of 2 hours
409+
# Per RelayAddress, we would announce these addrs to 2 destinations per day.
410+
# Since it's at most one rotation, at most 4 nodes can receive ADDR.
411+
assert_greater_than_or_equal(ADDR_DESTINATIONS_THRESHOLD, len(nodes_received_addr))
412+
self.nodes[0].disconnect_p2ps()
413+
414+
def destination_rotates_more_than_once_over_several_days_test(self):
415+
self.restart_node(0, [])
416+
417+
self.log.info('Test after several days an addr relay destination is rotated more than once')
418+
self.msg = self.setup_addr_msg(1)
419+
peer = self.nodes[0].add_p2p_connection(P2PInterface())
420+
receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
421+
addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)]
422+
# 10 intervals of 1 day (+ 1 hour, which should be enough to cover 30-min Poisson in most cases)
423+
nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, ONE_DAY, ONE_HOUR)
424+
# Now that there should have been more than one rotation, more than
425+
# ADDR_DESTINATIONS_THRESHOLD nodes should have received ADDR.
426+
assert_greater_than(len(nodes_received_addr), ADDR_DESTINATIONS_THRESHOLD)
427+
self.nodes[0].disconnect_p2ps()
428+
365429

366430
if __name__ == '__main__':
367431
AddrTest().main()

0 commit comments

Comments
 (0)