Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_addr_relay.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2020 The Bitcoin Core developers | # Copyright (c) 2020 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
""" | """ | ||||
Test addr relay | Test addr relay | ||||
""" | """ | ||||
import random | |||||
import time | import time | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
NODE_NETWORK, | NODE_NETWORK, | ||||
CAddress, | CAddress, | ||||
msg_addr, | msg_addr, | ||||
msg_getaddr, | msg_getaddr, | ||||
msg_verack, | msg_verack, | ||||
) | ) | ||||
from test_framework.p2p import P2PInterface | from test_framework.p2p import P2PInterface, p2p_lock | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import assert_equal, assert_greater_than | from test_framework.util import assert_equal, assert_greater_than | ||||
class AddrReceiver(P2PInterface): | class AddrReceiver(P2PInterface): | ||||
num_ipv4_received = 0 | num_ipv4_received = 0 | ||||
test_addr_contents = False | test_addr_contents = False | ||||
_tokens = 1 | |||||
send_getaddr = True | send_getaddr = True | ||||
def __init__(self, test_addr_contents=False, send_getaddr=True): | def __init__(self, test_addr_contents=False, send_getaddr=True): | ||||
super().__init__() | super().__init__() | ||||
self.test_addr_contents = test_addr_contents | self.test_addr_contents = test_addr_contents | ||||
self.send_getaddr = send_getaddr | self.send_getaddr = send_getaddr | ||||
def on_addr(self, message): | def on_addr(self, message): | ||||
for addr in message.addrs: | for addr in message.addrs: | ||||
self.num_ipv4_received += 1 | self.num_ipv4_received += 1 | ||||
if(self.test_addr_contents): | if(self.test_addr_contents): | ||||
# relay_tests checks the content of the addr messages match | # relay_tests checks the content of the addr messages match | ||||
# expectations based on the message creation in setup_addr_msg | # expectations based on the message creation in setup_addr_msg | ||||
assert_equal(addr.nServices, NODE_NETWORK) | assert_equal(addr.nServices, NODE_NETWORK) | ||||
if not 8333 <= addr.port < 8343: | if not 8333 <= addr.port < 8343: | ||||
raise AssertionError( | raise AssertionError( | ||||
"Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) | "Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) | ||||
assert addr.ip.startswith('123.123.123.') | assert addr.ip.startswith('123.123.123.') | ||||
def on_getaddr(self, message): | |||||
# When the node sends us a getaddr, it increments the addr relay tokens | |||||
# for the connection by 1000 | |||||
self._tokens += 1000 | |||||
@property | |||||
def tokens(self): | |||||
with p2p_lock: | |||||
return self._tokens | |||||
def increment_tokens(self, n): | |||||
# When we move mocktime forward, the node increments the addr relay | |||||
# tokens for its peers | |||||
with p2p_lock: | |||||
self._tokens += n | |||||
def addr_received(self): | def addr_received(self): | ||||
return self.num_ipv4_received != 0 | return self.num_ipv4_received != 0 | ||||
def on_version(self, message): | def on_version(self, message): | ||||
self.send_message(msg_verack()) | self.send_message(msg_verack()) | ||||
if (self.send_getaddr): | if (self.send_getaddr): | ||||
self.send_message(msg_getaddr()) | self.send_message(msg_getaddr()) | ||||
def getaddr_received(self): | def getaddr_received(self): | ||||
return self.message_count['getaddr'] > 0 | return self.message_count['getaddr'] > 0 | ||||
class AddrTest(BitcoinTestFramework): | class AddrTest(BitcoinTestFramework): | ||||
counter = 0 | counter = 0 | ||||
mocktime = int(time.time()) | mocktime = int(time.time()) | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.extra_args = [["-whitelist=addr@127.0.0.1"]] | |||||
def run_test(self): | def run_test(self): | ||||
self.oversized_addr_test() | self.oversized_addr_test() | ||||
self.relay_tests() | self.relay_tests() | ||||
self.inbound_blackhole_tests() | self.inbound_blackhole_tests() | ||||
# This test populates the addrman, which can impact the node's behavior | # This test populates the addrman, which can impact the node's behavior | ||||
# in subsequent tests | # in subsequent tests | ||||
self.getaddr_tests() | self.getaddr_tests() | ||||
self.blocksonly_mode_tests() | self.blocksonly_mode_tests() | ||||
self.rate_limit_tests() | |||||
def setup_addr_msg(self, num): | def setup_addr_msg(self, num): | ||||
addrs = [] | addrs = [] | ||||
for i in range(num): | for i in range(num): | ||||
addr = CAddress() | addr = CAddress() | ||||
addr.time = self.mocktime + i | addr.time = self.mocktime + i | ||||
addr.nServices = NODE_NETWORK | addr.nServices = NODE_NETWORK | ||||
addr.ip = f"123.123.123.{self.counter % 256}" | addr.ip = f"123.123.123.{self.counter % 256}" | ||||
addr.port = 8333 + i | addr.port = 8333 + i | ||||
addrs.append(addr) | addrs.append(addr) | ||||
self.counter += 1 | self.counter += 1 | ||||
msg = msg_addr() | msg = msg_addr() | ||||
msg.addrs = addrs | msg.addrs = addrs | ||||
return msg | return msg | ||||
def setup_rand_addr_msg(self, num): | |||||
addrs = [] | |||||
for i in range(num): | |||||
addr = CAddress() | |||||
addr.time = self.mocktime + i | |||||
addr.nServices = NODE_NETWORK | |||||
addr.ip = ( | |||||
f"{random.randrange(128,169)}.{random.randrange(1,255)}" | |||||
f".{random.randrange(1,255)}.{random.randrange(1,255)}" | |||||
) | |||||
addr.port = 8333 | |||||
addrs.append(addr) | |||||
msg = msg_addr() | |||||
msg.addrs = addrs | |||||
return msg | |||||
def send_addr_msg(self, source, msg, receivers): | def send_addr_msg(self, source, msg, receivers): | ||||
source.send_and_ping(msg) | source.send_and_ping(msg) | ||||
# pop m_next_addr_send timer | # pop m_next_addr_send timer | ||||
self.mocktime += 10 * 60 | self.mocktime += 10 * 60 | ||||
self.nodes[0].setmocktime(self.mocktime) | self.nodes[0].setmocktime(self.mocktime) | ||||
for peer in receivers: | for peer in receivers: | ||||
peer.sync_send_with_ping() | peer.sync_send_with_ping() | ||||
▲ Show 20 Lines • Show All 203 Lines • ▼ Show 20 Lines | def getaddr_tests(self): | ||||
assert_equal(full_outbound_peer.num_ipv4_received, 0) | assert_equal(full_outbound_peer.num_ipv4_received, 0) | ||||
assert_equal(block_relay_peer.num_ipv4_received, 0) | assert_equal(block_relay_peer.num_ipv4_received, 0) | ||||
assert inbound_peer.num_ipv4_received > 100 | assert inbound_peer.num_ipv4_received > 100 | ||||
self.nodes[0].disconnect_p2ps() | self.nodes[0].disconnect_p2ps() | ||||
def blocksonly_mode_tests(self): | def blocksonly_mode_tests(self): | ||||
self.log.info('Test addr relay in -blocksonly mode') | self.log.info('Test addr relay in -blocksonly mode') | ||||
self.restart_node(0, ["-blocksonly"]) | self.restart_node(0, ["-blocksonly", "-whitelist=addr@127.0.0.1"]) | ||||
self.mocktime = int(time.time()) | self.mocktime = int(time.time()) | ||||
self.log.info('Check that we send getaddr messages') | self.log.info('Check that we send getaddr messages') | ||||
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection( | full_outbound_peer = self.nodes[0].add_outbound_p2p_connection( | ||||
AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay") | AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay") | ||||
full_outbound_peer.sync_with_ping() | full_outbound_peer.sync_with_ping() | ||||
assert full_outbound_peer.getaddr_received() | assert full_outbound_peer.getaddr_received() | ||||
self.log.info('Check that we relay address messages') | self.log.info('Check that we relay address messages') | ||||
addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) | addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) | ||||
msg = self.setup_addr_msg(2) | msg = self.setup_addr_msg(2) | ||||
self.send_addr_msg(addr_source, msg, [full_outbound_peer]) | self.send_addr_msg(addr_source, msg, [full_outbound_peer]) | ||||
assert_equal(full_outbound_peer.num_ipv4_received, 2) | assert_equal(full_outbound_peer.num_ipv4_received, 2) | ||||
self.nodes[0].disconnect_p2ps() | self.nodes[0].disconnect_p2ps() | ||||
def send_addrs_and_test_rate_limiting(self, peer, no_relay, new_addrs, | |||||
total_addrs): | |||||
"""Send an addr message and check that the number of addresses processed | |||||
and rate-limited is as expected | |||||
""" | |||||
peer.send_and_ping(self.setup_rand_addr_msg(new_addrs)) | |||||
peerinfo = self.nodes[0].getpeerinfo()[0] | |||||
addrs_processed = peerinfo['addr_processed'] | |||||
addrs_rate_limited = peerinfo['addr_rate_limited'] | |||||
self.log.debug(f"addrs_processed = {addrs_processed}, " | |||||
f"addrs_rate_limited = {addrs_rate_limited}") | |||||
if no_relay: | |||||
assert_equal(addrs_processed, 0) | |||||
assert_equal(addrs_rate_limited, 0) | |||||
else: | |||||
assert_equal(addrs_processed, min(total_addrs, peer.tokens)) | |||||
assert_equal(addrs_rate_limited, max(0, total_addrs - peer.tokens)) | |||||
def rate_limit_tests(self): | |||||
self.mocktime = int(time.time()) | |||||
self.restart_node(0, []) | |||||
self.nodes[0].setmocktime(self.mocktime) | |||||
for contype, no_relay in [ | |||||
("outbound-full-relay", False), | |||||
("block-relay-only", True), | |||||
("inbound", False) | |||||
]: | |||||
self.log.info( | |||||
f'Test rate limiting of addr processing for {contype} peers') | |||||
if contype == "inbound": | |||||
peer = self.nodes[0].add_p2p_connection(AddrReceiver()) | |||||
else: | |||||
peer = self.nodes[0].add_outbound_p2p_connection( | |||||
AddrReceiver(), p2p_idx=0, connection_type=contype) | |||||
# Send 600 addresses. For all but the block-relay-only peer this | |||||
# should result in addresses being processed. | |||||
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 600) | |||||
# Send 600 more addresses. For the outbound-full-relay peer (which | |||||
# we send a GETADDR, and thus will process up to 1001 incoming | |||||
# addresses), this means more addresses will be processed. | |||||
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 1200) | |||||
# Send 10 more. As we reached the processing limit for all nodes, | |||||
# no more addresses should be procesesd. | |||||
self.send_addrs_and_test_rate_limiting(peer, no_relay, 10, 1210) | |||||
# Advance the time by 100 seconds, permitting the processing of 10 | |||||
# more addresses. | |||||
# Send 200 and verify that 10 are processed. | |||||
self.mocktime += 100 | |||||
self.nodes[0].setmocktime(self.mocktime) | |||||
peer.increment_tokens(10) | |||||
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1410) | |||||
# Advance the time by 1000 seconds, permitting the processing of 100 | |||||
# more addresses. | |||||
# Send 200 and verify that 100 are processed. | |||||
self.mocktime += 1000 | |||||
self.nodes[0].setmocktime(self.mocktime) | |||||
peer.increment_tokens(100) | |||||
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1610) | |||||
self.nodes[0].disconnect_p2ps() | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AddrTest().main() | AddrTest().main() |