diff --git a/test/functional/abc_p2p_compactproofs.py b/test/functional/abc_p2p_compactproofs.py index e8838ee5a..dd1930fb1 100644 --- a/test/functional/abc_p2p_compactproofs.py +++ b/test/functional/abc_p2p_compactproofs.py @@ -1,688 +1,688 @@ #!/usr/bin/env python3 # Copyright (c) 2022 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test proof inventory relaying """ import random import time from test_framework.avatools import ( AvaP2PInterface, NoHandshakeAvaP2PInterface, build_msg_avaproofs, gen_proof, get_ava_p2p_interface, get_proof_ids, wait_for_proof, ) from test_framework.messages import ( NODE_AVALANCHE, NODE_NETWORK, AvalanchePrefilledProof, calculate_shortid, msg_avaproofsreq, msg_getavaproofs, ) from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import MAX_NODES, assert_equal, p2p_port, uint256_hex # Timeout after which the proofs can be cleaned up AVALANCHE_AVAPROOFS_TIMEOUT = 2 * 60 # Max interval between 2 periodic networking processing AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL = 5 * 60 class ProofStoreP2PInterface(AvaP2PInterface): def __init__(self): self.proofs = [] super().__init__() def on_avaproof(self, message): self.proofs.append(message.proof) def get_proofs(self): with p2p_lock: return self.proofs class CompactProofsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [[ '-avalanche=1', '-avaproofstakeutxodustthreshold=1000000', '-avaproofstakeutxoconfirmations=1', '-avacooldown=0', ]] * self.num_nodes def setup_network(self): # Don't connect the nodes self.setup_nodes() @staticmethod def received_avaproofs(peer): with p2p_lock: return peer.last_message.get("avaproofs") def test_send_outbound_getavaproofs(self): self.log.info( "Check we send a getavaproofs message to our avalanche outbound peers") node = self.nodes[0] p2p_idx = 0 non_avapeers = [] for _ in range(4): peer = P2PInterface() node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, connection_type="outbound-full-relay", services=NODE_NETWORK, ) non_avapeers.append(peer) p2p_idx += 1 inbound_avapeers = [ node.add_p2p_connection( NoHandshakeAvaP2PInterface()) for _ in range(4)] outbound_avapeers = [] # With a proof and the service bit set for _ in range(4): peer = AvaP2PInterface(node) node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) outbound_avapeers.append(peer) p2p_idx += 1 # Without a proof and no service bit set for _ in range(4): peer = AvaP2PInterface() node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, connection_type="outbound-full-relay", services=NODE_NETWORK, ) outbound_avapeers.append(peer) p2p_idx += 1 def all_peers_received_getavaproofs(): with p2p_lock: return all([p.last_message.get("getavaproofs") for p in outbound_avapeers]) self.wait_until(all_peers_received_getavaproofs) with p2p_lock: assert all([p.message_count.get( "getavaproofs", 0) >= 1 for p in outbound_avapeers]) assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in non_avapeers]) assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in inbound_avapeers]) self.log.info( "Check we send periodic getavaproofs message to some of our peers") def count_outbounds_getavaproofs(): with p2p_lock: return sum([p.message_count.get("getavaproofs", 0) for p in outbound_avapeers]) outbounds_getavaproofs = count_outbounds_getavaproofs() node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) self.wait_until(lambda: count_outbounds_getavaproofs() == outbounds_getavaproofs + 3) outbounds_getavaproofs += 3 with p2p_lock: assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in non_avapeers]) assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in inbound_avapeers]) self.log.info( "After the first avaproofs has been received, all the peers are requested periodically") responding_outbound_avapeer = AvaP2PInterface(node) node.add_outbound_p2p_connection( responding_outbound_avapeer, p2p_idx=p2p_idx, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) p2p_idx += 1 responding_outbound_avapeer_id = node.getpeerinfo()[-1]['id'] outbound_avapeers.append(responding_outbound_avapeer) self.wait_until(all_peers_received_getavaproofs) # Register as an avalanche node for the avaproofs message to be counted key, proof = gen_proof(node) assert node.addavalanchenode( responding_outbound_avapeer_id, key.get_pubkey().get_bytes().hex(), proof.serialize().hex()) # Send the avaproofs message avaproofs = build_msg_avaproofs([proof]) responding_outbound_avapeer.send_and_ping(avaproofs) # Now the node will request from all its peers at each time period outbounds_getavaproofs = count_outbounds_getavaproofs() num_outbound_avapeers = len(outbound_avapeers) node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) self.wait_until(lambda: count_outbounds_getavaproofs() == outbounds_getavaproofs + num_outbound_avapeers) outbounds_getavaproofs += num_outbound_avapeers self.log.info("Empty avaproofs will not trigger any request") for p in outbound_avapeers: p.send_message(build_msg_avaproofs([])) with p2p_lock: # Only this peer actually sent a proof assert_equal( responding_outbound_avapeer.message_count.get( "avaproofsreq", 0), 1) assert_equal(sum([p.message_count.get("avaproofsreq", 0) for p in outbound_avapeers]), 1) # Sanity checks assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in non_avapeers]) assert all([p.message_count.get( "getavaproofs", 0) == 0 for p in inbound_avapeers]) def test_send_manual_getavaproofs(self): self.log.info( "Check we send a getavaproofs message to our manually connected peers that support avalanche") node = self.nodes[0] # Get rid of previously connected nodes node.disconnect_p2ps() def added_node_connected(ip_port): added_node_info = node.getaddednodeinfo(ip_port) return len( added_node_info) == 1 and added_node_info[0]['connected'] def connect_callback(address, port): self.log.debug("Connecting to {}:{}".format(address, port)) p = AvaP2PInterface(node) p2p_idx = 1 p.peer_accept_connection( connect_cb=connect_callback, connect_id=p2p_idx, net=node.chain, timeout_factor=node.timeout_factor, services=NODE_NETWORK | NODE_AVALANCHE, )() - ip_port = f"127.0.01:{p2p_port(MAX_NODES - p2p_idx)}" + ip_port = f"127.0.0.1:{p2p_port(MAX_NODES - p2p_idx)}" node.addnode(node=ip_port, command="add") self.wait_until(lambda: added_node_connected(ip_port)) assert_equal(node.getpeerinfo()[-1]['addr'], ip_port) assert_equal(node.getpeerinfo()[-1]['connection_type'], 'manual') p.wait_until(lambda: p.last_message.get("getavaproofs")) def test_respond_getavaproofs(self): self.log.info("Check the node responds to getavaproofs messages") node = self.nodes[0] def send_getavaproof_check_shortid_len(peer, expected_len): peer.send_message(msg_getavaproofs()) self.wait_until(lambda: self.received_avaproofs(peer)) avaproofs = self.received_avaproofs(peer) assert_equal(len(avaproofs.shortids), expected_len) # Initially the node has 0 peer self.restart_node(0) assert_equal(len(get_proof_ids(node)), 0) peer = node.add_p2p_connection(NoHandshakeAvaP2PInterface()) send_getavaproof_check_shortid_len(peer, 0) # Add some proofs sending_peer = node.add_p2p_connection(NoHandshakeAvaP2PInterface()) for _ in range(50): _, proof = gen_proof(node) sending_peer.send_avaproof(proof) wait_for_proof(node, uint256_hex(proof.proofid)) proofids = get_proof_ids(node) assert_equal(len(proofids), 50) receiving_peer = node.add_p2p_connection(NoHandshakeAvaP2PInterface()) send_getavaproof_check_shortid_len(receiving_peer, len(proofids)) avaproofs = self.received_avaproofs(receiving_peer) expected_shortids = [ calculate_shortid( avaproofs.key0, avaproofs.key1, proofid) for proofid in sorted(proofids)] assert_equal(expected_shortids, avaproofs.shortids) # Don't expect any prefilled proof for now assert_equal(len(avaproofs.prefilled_proofs), 0) def test_request_missing_proofs(self): self.log.info( "Check the node requests the missing proofs after receiving an avaproofs message") node = self.nodes[0] self.restart_node(0) key0 = random.randint(0, 2**64 - 1) key1 = random.randint(0, 2**64 - 1) proofs = [gen_proof(node)[1] for _ in range(10)] # Build a map from proofid to shortid. Use sorted proofids so we don't # have the same indices than the `proofs` list. proofids = [p.proofid for p in proofs] shortid_map = {} for proofid in sorted(proofids): shortid_map[proofid] = calculate_shortid(key0, key1, proofid) self.log.info("The node ignores unsollicited avaproofs") spam_peer = get_ava_p2p_interface(node) msg = build_msg_avaproofs( proofs, prefilled_proofs=[], key_pair=[ key0, key1]) with node.assert_debug_log(["Ignoring unsollicited avaproofs"]): spam_peer.send_message(msg) def received_avaproofsreq(peer): with p2p_lock: return peer.last_message.get("avaproofsreq") p2p_idx = 0 def add_avalanche_p2p_outbound(): nonlocal p2p_idx peer = AvaP2PInterface(node) node.add_outbound_p2p_connection( peer, p2p_idx=p2p_idx, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) p2p_idx += 1 peer.wait_until(lambda: peer.last_message.get("getavaproofs")) return peer def expect_indices(shortids, expected_indices, prefilled_proofs=None): nonlocal p2p_idx msg = build_msg_avaproofs( [], prefilled_proofs=prefilled_proofs, key_pair=[ key0, key1]) msg.shortids = shortids peer = add_avalanche_p2p_outbound() peer.send_message(msg) self.wait_until(lambda: received_avaproofsreq(peer)) avaproofsreq = received_avaproofsreq(peer) assert_equal(avaproofsreq.indices, expected_indices) self.log.info("Check no proof is requested if there is no shortid") msg = build_msg_avaproofs([]) sender = add_avalanche_p2p_outbound() with node.assert_debug_log(["Got an avaproofs message with no shortid"]): sender.send_message(msg) # Make sure we don't get an avaproofsreq message sender.sync_send_with_ping() with p2p_lock: assert_equal(sender.message_count.get("avaproofsreq", 0), 0) self.log.info( "Check the node requests all the proofs if it known none") expect_indices( list(shortid_map.values()), [i for i in range(len(shortid_map))] ) self.log.info( "Check the node requests only the missing proofs") known_proofids = [] for proof in proofs[:5]: node.sendavalancheproof(proof.serialize().hex()) known_proofids.append(proof.proofid) expected_indices = [i for i, proofid in enumerate( shortid_map) if proofid not in known_proofids] expect_indices(list(shortid_map.values()), expected_indices) self.log.info( "Check the node don't request prefilled proofs") # Get the indices for a couple of proofs indice_proof5 = list(shortid_map.keys()).index(proofids[5]) indice_proof6 = list(shortid_map.keys()).index(proofids[6]) prefilled_proofs = [ AvalanchePrefilledProof(indice_proof5, proofs[5]), AvalanchePrefilledProof(indice_proof6, proofs[6]), ] prefilled_proofs = sorted( prefilled_proofs, key=lambda prefilled_proof: prefilled_proof.index) remaining_shortids = [shortid for proofid, shortid in shortid_map.items( ) if proofid not in proofids[5:7]] known_proofids.extend(proofids[5:7]) expected_indices = [i for i, proofid in enumerate( shortid_map) if proofid not in known_proofids] expect_indices( remaining_shortids, expected_indices, prefilled_proofs=prefilled_proofs) self.log.info( "Check the node requests no proof if it knows all of them") for proof in proofs[5:]: node.sendavalancheproof(proof.serialize().hex()) known_proofids.append(proof.proofid) expect_indices(list(shortid_map.values()), []) self.log.info("Check out of bounds index") bad_peer = add_avalanche_p2p_outbound() msg = build_msg_avaproofs([], prefilled_proofs=[ AvalanchePrefilledProof( len(shortid_map) + 1, gen_proof(node)[1])], key_pair=[key0, key1]) msg.shortids = list(shortid_map.values()) with node.assert_debug_log(["Misbehaving", "avaproofs-bad-indexes"]): bad_peer.send_message(msg) bad_peer.wait_for_disconnect() self.log.info("An invalid prefilled proof will trigger a ban") _, no_stake = gen_proof(node) no_stake.stakes = [] bad_peer = add_avalanche_p2p_outbound() msg = build_msg_avaproofs([], prefilled_proofs=[ AvalanchePrefilledProof(len(shortid_map), no_stake), ], key_pair=[key0, key1]) msg.shortids = list(shortid_map.values()) with node.assert_debug_log(["Misbehaving", "invalid-proof"]): bad_peer.send_message(msg) bad_peer.wait_for_disconnect() def test_send_missing_proofs(self): self.log.info("Check the node respond to missing proofs requests") node = self.nodes[0] self.restart_node(0) numof_proof = 10 proofs = [gen_proof(node)[1] for _ in range(numof_proof)] for proof in proofs: node.sendavalancheproof(proof.serialize().hex()) proofids = get_proof_ids(node) assert all(proof.proofid in proofids for proof in proofs) self.log.info("Unsollicited requests are ignored") peer = node.add_p2p_connection(ProofStoreP2PInterface()) peer.send_and_ping(msg_avaproofsreq()) assert_equal(len(peer.get_proofs()), 0) def request_proofs(peer): peer.send_message(msg_getavaproofs()) self.wait_until(lambda: self.received_avaproofs(peer)) avaproofs = self.received_avaproofs(peer) assert_equal(len(avaproofs.shortids), numof_proof) return avaproofs _ = request_proofs(peer) self.log.info("Sending an empty request has no effect") peer.send_and_ping(msg_avaproofsreq()) assert_equal(len(peer.get_proofs()), 0) self.log.info("Check the requested proofs are sent by the node") def check_received_proofs(indices): requester = node.add_p2p_connection(ProofStoreP2PInterface()) avaproofs = request_proofs(requester) req = msg_avaproofsreq() req.indices = indices requester.send_message(req) # Check we got the expected number of proofs self.wait_until( lambda: len( requester.get_proofs()) == len(indices)) # Check we got the expected proofs received_shortids = [ calculate_shortid( avaproofs.key0, avaproofs.key1, proof.proofid) for proof in requester.get_proofs()] assert_equal(set(received_shortids), set([avaproofs.shortids[i] for i in indices])) # Only the first proof check_received_proofs([0]) # Only the last proof check_received_proofs([numof_proof - 1]) # Half first check_received_proofs(range(0, numof_proof // 2)) # Half last check_received_proofs(range(numof_proof // 2, numof_proof)) # Even check_received_proofs([i for i in range(numof_proof) if i % 2 == 0]) # Odds check_received_proofs([i for i in range(numof_proof) if i % 2 == 1]) # All check_received_proofs(range(numof_proof)) self.log.info( "Check the node will not send the proofs if not requested before the timeout elapsed") # Disconnect the peers for peer in node.p2ps: peer.peer_disconnect() peer.wait_for_disconnect() mocktime = int(time.time()) node.setmocktime(mocktime) slow_peer = ProofStoreP2PInterface() node.add_outbound_p2p_connection( slow_peer, p2p_idx=0, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) slow_peer.wait_until( lambda: slow_peer.last_message.get("getavaproofs")) slow_peer.nodeid = node.getpeerinfo()[-1]['id'] _ = request_proofs(slow_peer) # Elapse the timeout mocktime += AVALANCHE_AVAPROOFS_TIMEOUT + 1 node.setmocktime(mocktime) node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) # Periodic compact proofs requests are sent in the same loop than the # cleanup, so when such a request is made we are sure the cleanup did # happen. slow_peer.wait_until( lambda: slow_peer.message_count.get("getavaproofs") > 1) req = msg_avaproofsreq() req.indices = range(numof_proof) slow_peer.send_and_ping(req) # Check we get no proof assert_equal(len(slow_peer.get_proofs()), 0) def test_compact_proofs_download_on_connect(self): self.log.info( "Check the node get compact proofs upon avalanche outbound discovery") requestee = self.nodes[0] requester = self.nodes[1] self.restart_node(0) numof_proof = 10 proofs = [gen_proof(requestee)[1] for _ in range(numof_proof)] for proof in proofs: requestee.sendavalancheproof(proof.serialize().hex()) proofids = get_proof_ids(requestee) assert all(proof.proofid in proofids for proof in proofs) # Start the requester and check it gets all the proofs self.start_node(1) self.connect_nodes(0, 1) self.wait_until( lambda: all( proof.proofid in proofids for proof in get_proof_ids(requester))) def test_no_compactproofs_during_ibs(self): self.log.info( "Check the node don't request compact proofs during IBD") node = self.nodes[0] chainwork = int(node.getblockchaininfo()['chainwork'], 16) self.restart_node( 0, extra_args=self.extra_args[0] + [f'-minimumchainwork={chainwork + 2:#x}']) assert node.getblockchaininfo()['initialblockdownload'] peer = P2PInterface() node.add_outbound_p2p_connection( peer, p2p_idx=0, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) # Force the node to process the sending loop peer.sync_send_with_ping() with p2p_lock: assert_equal(peer.message_count.get("getavaproofs", 0), 0) # Make sure there is no message sent as part as the periodic network # messaging either node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) peer.sync_send_with_ping() with p2p_lock: assert_equal(peer.message_count.get("getavaproofs", 0), 0) def test_send_inbound_getavaproofs_until_quorum_is_established(self): self.log.info( "Check we also request the inbounds until the quorum is established") node = self.nodes[0] self.restart_node( 0, extra_args=self.extra_args[0] + ['-avaminquorumstake=1000000', '-avaminavaproofsnodecount=0']) assert_equal(node.getavalancheinfo()['ready_to_poll'], False) outbound = AvaP2PInterface() node.add_outbound_p2p_connection(outbound, p2p_idx=0) inbound = AvaP2PInterface() node.add_p2p_connection(inbound) inbound.nodeid = node.getpeerinfo()[-1]['id'] def count_getavaproofs(peers): with p2p_lock: return sum([peer.message_count.get("getavaproofs", 0) for peer in peers]) # Upon connection only the outbound gets a compact proofs message assert_equal(count_getavaproofs([inbound]), 0) self.wait_until(lambda: count_getavaproofs([outbound]) == 1) # Periodic send will include the inbound as well current_total = count_getavaproofs([inbound, outbound]) while count_getavaproofs([inbound]) == 0: node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) self.wait_until(lambda: count_getavaproofs( [inbound, outbound]) > current_total) current_total = count_getavaproofs([inbound, outbound]) # Connect the minimum amount of stake and nodes for _ in range(8): node.add_p2p_connection(AvaP2PInterface(node)) self.wait_until(lambda: node.getavalancheinfo() ['ready_to_poll'] is True) # From now only the outbound is requested count_inbound = count_getavaproofs([inbound]) while count_getavaproofs([inbound, outbound]) < current_total + 20: node.mockscheduler(AVALANCHE_MAX_PERIODIC_NETWORKING_INTERVAL) inbound.sync_send_with_ping() outbound.sync_send_with_ping() assert_equal(count_getavaproofs([inbound]), count_inbound) def run_test(self): # Most if the tests only need a single node, let the other ones start # the node when required self.stop_node(1) self.test_send_outbound_getavaproofs() self.test_send_manual_getavaproofs() self.test_respond_getavaproofs() self.test_request_missing_proofs() self.test_send_missing_proofs() self.test_compact_proofs_download_on_connect() self.test_no_compactproofs_during_ibs() self.test_send_inbound_getavaproofs_until_quorum_is_established() if __name__ == '__main__': CompactProofsTest().main() diff --git a/test/functional/abc_p2p_getavaaddr.py b/test/functional/abc_p2p_getavaaddr.py index 38a9e0d23..e147676fa 100755 --- a/test/functional/abc_p2p_getavaaddr.py +++ b/test/functional/abc_p2p_getavaaddr.py @@ -1,517 +1,517 @@ #!/usr/bin/env python3 # Copyright (c) 2022 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test getavaaddr p2p message""" import time from decimal import Decimal from test_framework.avatools import AvaP2PInterface, gen_proof from test_framework.messages import ( NODE_AVALANCHE, NODE_NETWORK, AvalancheVote, AvalancheVoteError, msg_avahello, msg_getaddr, msg_getavaaddr, msg_verack, ) from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import MAX_NODES, assert_equal, p2p_port, uint256_hex # getavaaddr time interval in seconds, as defined in net_processing.cpp # A node will ignore repeated getavaaddr during this interval GETAVAADDR_INTERVAL = 2 * 60 # Address are sent every 30s on average, with a Poisson filter. Use a large # enough delay so it's very unlikely we don't get the message within this time. MAX_ADDR_SEND_DELAY = 10 * 60 # The interval between avalanche statistics computation AVALANCHE_STATISTICS_INTERVAL = 10 * 60 # The getavaaddr messages are sent every 2 to 5 minutes MAX_GETAVAADDR_DELAY = 5 * 60 class AddrReceiver(P2PInterface): def __init__(self): super().__init__() self.received_addrs = None def get_received_addrs(self): with p2p_lock: return self.received_addrs def on_addr(self, message): self.received_addrs = [] for addr in message.addrs: self.received_addrs.append(f"{addr.ip}:{addr.port}") def addr_received(self): return self.received_addrs is not None class MutedAvaP2PInterface(AvaP2PInterface): def __init__(self, node=None): super().__init__(node) self.is_responding = False self.privkey = None self.addr = None self.poll_received = 0 def set_addr(self, addr): self.addr = addr def on_avapoll(self, message): self.poll_received += 1 class AllYesAvaP2PInterface(MutedAvaP2PInterface): def __init__(self, node=None): super().__init__(node) self.is_responding = True def on_avapoll(self, message): self.send_avaresponse( message.poll.round, [ AvalancheVote( AvalancheVoteError.ACCEPTED, inv.hash) for inv in message.poll.invs], self.master_privkey if self.delegation is None else self.delegated_privkey) super().on_avapoll(message) class AvaHelloInterface(AvaP2PInterface): def __init__(self): super().__init__() def on_version(self, message): self.send_message(msg_verack()) self.send_message(msg_avahello()) class AvaAddrTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 self.extra_args = [ [ '-avalanche=1', '-avaproofstakeutxodustthreshold=1000000', '-avaproofstakeutxoconfirmations=1', '-avacooldown=0', '-avaminquorumstake=0', '-avaminavaproofsnodecount=0', '-whitelist=noban@127.0.0.1', ] ] def check_all_peers_received_getavaaddr_once(self, avapeers): def received_all_getavaaddr(avapeers): with p2p_lock: return all([p.last_message.get("getavaaddr") for p in avapeers]) self.wait_until(lambda: received_all_getavaaddr(avapeers)) with p2p_lock: assert all([p.message_count.get( "getavaaddr", 0) == 1 for p in avapeers]) def getavaaddr_interval_test(self): node = self.nodes[0] # Init mock time mock_time = int(time.time()) node.setmocktime(mock_time) # Add some avalanche peers to the node for _ in range(10): node.add_p2p_connection(AllYesAvaP2PInterface(node)) # Build some statistics to ensure some addresses will be returned def all_peers_received_poll(): with p2p_lock: return all([avanode.poll_received > 0 for avanode in node.p2ps]) self.wait_until(all_peers_received_poll) node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) requester = node.add_p2p_connection(AddrReceiver()) requester.send_message(msg_getavaaddr()) # Remember the time we sent the getavaaddr message getavaddr_time = mock_time # Spamming more get getavaaddr has no effect for _ in range(10): with node.assert_debug_log(["Ignoring repeated getavaaddr from peer"]): requester.send_message(msg_getavaaddr()) # Move the time so we get an addr response mock_time += MAX_ADDR_SEND_DELAY node.setmocktime(mock_time) requester.wait_until(requester.addr_received) # Elapse the getavaaddr interval and check our message is now accepted # again mock_time = getavaddr_time + GETAVAADDR_INTERVAL node.setmocktime(mock_time) requester.send_message(msg_getavaaddr()) # We can get an addr message again mock_time += MAX_ADDR_SEND_DELAY node.setmocktime(mock_time) requester.wait_until(requester.addr_received) def address_test(self, maxaddrtosend, num_proof, num_avanode): self.restart_node( 0, extra_args=self.extra_args[0] + [f'-maxaddrtosend={maxaddrtosend}']) node = self.nodes[0] # Init mock time mock_time = int(time.time()) node.setmocktime(mock_time) # Create a bunch of proofs and associate each a bunch of nodes. avanodes = [] for _ in range(num_proof): master_privkey, proof = gen_proof(node) for n in range(num_avanode): avanode = AllYesAvaP2PInterface() if n % 2 else MutedAvaP2PInterface() avanode.master_privkey = master_privkey avanode.proof = proof node.add_p2p_connection(avanode) peerinfo = node.getpeerinfo()[-1] avanode.set_addr(peerinfo["addr"]) avanodes.append(avanode) responding_addresses = [ avanode.addr for avanode in avanodes if avanode.is_responding] assert_equal(len(responding_addresses), num_proof * num_avanode // 2) # Check we have what we expect def all_nodes_connected(): avapeers = node.getavalanchepeerinfo() if len(avapeers) != num_proof: return False for avapeer in avapeers: if avapeer['nodecount'] != num_avanode: return False return True self.wait_until(all_nodes_connected) # Force the availability score to diverge between the responding and the # muted nodes. node.generate(1) def poll_all_for_block(): with p2p_lock: return all([avanode.poll_received > ( 10 if avanode.is_responding else 0) for avanode in avanodes]) self.wait_until(poll_all_for_block) # Move the scheduler time 10 minutes forward so that so that our peers # get an availability score computed. node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) requester = node.add_p2p_connection(AddrReceiver()) requester.send_and_ping(msg_getavaaddr()) # Sanity check that the availability score is set up as expected peerinfo = node.getpeerinfo() muted_addresses = [ avanode.addr for avanode in avanodes if not avanode.is_responding] assert all([p['availability_score'] < 0 for p in peerinfo if p["addr"] in muted_addresses]) assert all([p['availability_score'] > 0 for p in peerinfo if p["addr"] in responding_addresses]) # Requester has no availability_score because it's not an avalanche # peer assert 'availability_score' not in peerinfo[-1].keys() mock_time += MAX_ADDR_SEND_DELAY node.setmocktime(mock_time) requester.wait_until(requester.addr_received) addresses = requester.get_received_addrs() assert_equal(len(addresses), min(maxaddrtosend, len(responding_addresses))) # Check all the addresses belong to responding peer assert all([address in responding_addresses for address in addresses]) def getavaaddr_outbound_test(self): self.log.info( "Check we send a getavaaddr message to our avalanche outbound peers") node = self.nodes[0] # Get rid of previously connected nodes node.disconnect_p2ps() avapeers = [] for i in range(16): avapeer = AvaP2PInterface() node.add_outbound_p2p_connection( avapeer, p2p_idx=i, ) avapeers.append(avapeer) self.check_all_peers_received_getavaaddr_once(avapeers) # Generate some block to poll for node.generate(1) # Because none of the avalanche peers is responding, our node should # fail out of option shortly and send a getavaaddr message to its # outbound avalanche peers. node.mockscheduler(MAX_GETAVAADDR_DELAY) def all_peers_received_getavaaddr(): with p2p_lock: return all([p.message_count.get( "getavaaddr", 0) > 1 for p in avapeers]) self.wait_until(all_peers_received_getavaaddr) def getavaaddr_manual_test(self): self.log.info( "Check we send a getavaaddr message to our manually connected peers that support avalanche") node = self.nodes[0] # Get rid of previously connected nodes node.disconnect_p2ps() def added_node_connected(ip_port): added_node_info = node.getaddednodeinfo(ip_port) return len( added_node_info) == 1 and added_node_info[0]['connected'] def connect_callback(address, port): self.log.debug("Connecting to {}:{}".format(address, port)) p = AvaP2PInterface() p2p_idx = 1 p.peer_accept_connection( connect_cb=connect_callback, connect_id=p2p_idx, net=node.chain, timeout_factor=node.timeout_factor, )() - ip_port = f"127.0.01:{p2p_port(MAX_NODES - p2p_idx)}" + ip_port = f"127.0.0.1:{p2p_port(MAX_NODES - p2p_idx)}" node.addnode(node=ip_port, command="add") self.wait_until(lambda: added_node_connected(ip_port)) assert_equal(node.getpeerinfo()[-1]['addr'], ip_port) assert_equal(node.getpeerinfo()[-1]['connection_type'], 'manual') p.wait_until(lambda: p.last_message.get("getavaaddr")) # Generate some block to poll for node.generate(1) # Because our avalanche peer is not responding, our node should fail # out of option shortly and send another getavaaddr message. node.mockscheduler(MAX_GETAVAADDR_DELAY) p.wait_until(lambda: p.message_count.get("getavaaddr", 0) > 1) def getavaaddr_noquorum(self): self.log.info( "Check we send a getavaaddr message while our quorum is not established") node = self.nodes[0] self.restart_node(0, extra_args=self.extra_args[0] + [ '-avaminquorumstake=500000000', '-avaminquorumconnectedstakeratio=0.8', ]) avapeers = [] for i in range(16): avapeer = AllYesAvaP2PInterface(node) node.add_outbound_p2p_connection( avapeer, p2p_idx=i, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) avapeers.append(avapeer) peerinfo = node.getpeerinfo()[-1] avapeer.set_addr(peerinfo["addr"]) self.check_all_peers_received_getavaaddr_once(avapeers) def total_getavaaddr_msg(): with p2p_lock: return sum([p.message_count.get("getavaaddr", 0) for p in avapeers]) # Because we have not enough stake to start polling, we keep requesting # more addresses from all our peers total_getavaaddr = total_getavaaddr_msg() node.mockscheduler(MAX_GETAVAADDR_DELAY) self.wait_until(lambda: total_getavaaddr_msg() == total_getavaaddr + len(avapeers)) # Move the schedulter time forward to make sure we get statistics # computed. But since we did not start polling yet it should remain all # zero. node.mockscheduler(AVALANCHE_STATISTICS_INTERVAL) def wait_for_availability_score(): peerinfo = node.getpeerinfo() return all([p.get('availability_score', None) == Decimal(0) for p in peerinfo]) self.wait_until(wait_for_availability_score) requester = node.add_p2p_connection(AddrReceiver()) requester.send_and_ping(msg_getavaaddr()) node.setmocktime(int(time.time() + MAX_ADDR_SEND_DELAY)) # Check all the peers addresses are returned. requester.wait_until(requester.addr_received) addresses = requester.get_received_addrs() assert_equal(len(addresses), len(avapeers)) expected_addresses = [avapeer.addr for avapeer in avapeers] assert all([address in expected_addresses for address in addresses]) # Add more nodes so we reach the mininum quorum stake amount. for _ in range(4): avapeer = AllYesAvaP2PInterface(node) node.add_p2p_connection(avapeer) self.wait_until(lambda: node.getavalancheinfo() ['ready_to_poll'] is True) def is_vote_finalized(proof): return node.getrawavalancheproof( uint256_hex(proof.proofid)).get("finalized", False) # Wait until all proofs are finalized self.wait_until(lambda: all([is_vote_finalized(p.proof) for p in node.p2ps if isinstance(p, AvaP2PInterface)])) # Go through several rounds of getavaaddr requests. We don't know for # sure how many will be sent as it depends on whether the peers # responded fast enough during the polling phase, but at some point a # single outbound peer will be requested and no more. def sent_single_getavaaddr(): total_getavaaddr = total_getavaaddr_msg() node.mockscheduler(MAX_GETAVAADDR_DELAY) self.wait_until(lambda: total_getavaaddr_msg() >= total_getavaaddr + 1) for p in avapeers: p.sync_send_with_ping() return total_getavaaddr_msg() == total_getavaaddr + 1 self.wait_until(sent_single_getavaaddr) def test_send_inbound_getavaaddr_until_quorum_is_established(self): self.log.info( "Check we also request the inbounds until the quorum is established") node = self.nodes[0] self.restart_node( 0, extra_args=self.extra_args[0] + ['-avaminquorumstake=1000000']) assert_equal(node.getavalancheinfo()['ready_to_poll'], False) outbound = MutedAvaP2PInterface() node.add_outbound_p2p_connection(outbound, p2p_idx=0) inbound = MutedAvaP2PInterface() node.add_p2p_connection(inbound) inbound.nodeid = node.getpeerinfo()[-1]['id'] def count_getavaaddr(peers): with p2p_lock: return sum([peer.message_count.get("getavaaddr", 0) for peer in peers]) # Upon connection only the outbound gets a getavaaddr message assert_equal(count_getavaaddr([inbound]), 0) self.wait_until(lambda: count_getavaaddr([outbound]) == 1) # Periodic send will include the inbound as well current_total = count_getavaaddr([inbound, outbound]) while count_getavaaddr([inbound]) == 0: node.mockscheduler(MAX_GETAVAADDR_DELAY) self.wait_until(lambda: count_getavaaddr( [inbound, outbound]) > current_total) current_total = count_getavaaddr([inbound, outbound]) # Connect the minimum amount of stake and nodes for _ in range(8): node.add_p2p_connection(AvaP2PInterface(node)) self.wait_until(lambda: node.getavalancheinfo() ['ready_to_poll'] is True) # From now only the outbound is requested count_inbound = count_getavaaddr([inbound]) for _ in range(10): # Trigger a poll node.generate(1) inbound.sync_send_with_ping() node.mockscheduler(MAX_GETAVAADDR_DELAY) self.wait_until(lambda: count_getavaaddr( [inbound, outbound]) > current_total) current_total = count_getavaaddr([inbound, outbound]) assert_equal(count_getavaaddr([inbound]), count_inbound) def test_addr_requests_order(self): node = self.nodes[0] # Get rid of previously connected nodes node.disconnect_p2ps() def check_addr_requests(p): p.wait_until(lambda: p.last_message.get("getavaaddr")) p.wait_until(lambda: p.message_count.get("getavaaddr", 0) == 1) p.wait_until(lambda: p.last_message.get("getaddr")) p.wait_until(lambda: p.message_count.get("getaddr", 0) == 1) # Test getaddr is sent first requester1 = node.add_outbound_p2p_connection( AvaHelloInterface(), p2p_idx=0) requester1.send_message(msg_getaddr()) requester1.send_message(msg_getavaaddr()) check_addr_requests(requester1) # Test getavaaddr is sent first requester2 = node.add_outbound_p2p_connection( AvaHelloInterface(), p2p_idx=1) requester2.send_message(msg_getavaaddr()) requester2.send_message(msg_getaddr()) check_addr_requests(requester2) def run_test(self): self.getavaaddr_interval_test() # Limited by maxaddrtosend self.address_test(maxaddrtosend=3, num_proof=2, num_avanode=8) # Limited by the number of good nodes self.address_test(maxaddrtosend=100, num_proof=2, num_avanode=8) self.getavaaddr_outbound_test() self.getavaaddr_manual_test() self.getavaaddr_noquorum() self.test_send_inbound_getavaaddr_until_quorum_is_established() self.test_addr_requests_order() if __name__ == '__main__': AvaAddrTest().main()