diff --git a/test/functional/abc_p2p_compactproofs.py b/test/functional/abc_p2p_compactproofs.py index 8e85d02e1..f5ee5f951 100644 --- a/test/functional/abc_p2p_compactproofs.py +++ b/test/functional/abc_p2p_compactproofs.py @@ -1,163 +1,163 @@ #!/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 """ from test_framework.avatools import ( AvaP2PInterface, gen_proof, get_proof_ids, wait_for_proof, ) from test_framework.messages import ( NODE_AVALANCHE, NODE_NETWORK, + calculate_shortid, msg_getavaproofs, ) from test_framework.p2p import P2PInterface, p2p_lock -from test_framework.siphash import siphash256 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import MAX_NODES, assert_equal, p2p_port class CompactProofsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [[ '-enableavalanche=1', '-avacooldown=0', '-whitelist=noban@127.0.0.1', ]] * self.num_nodes def test_send_outbound_getavaproofs(self): self.log.info( "Check we send a getavaproofs message to our avalanche outbound peers") node = self.nodes[0] non_avapeers = [] for i in range(4): peer = P2PInterface() node.add_outbound_p2p_connection( peer, p2p_idx=i, connection_type="outbound-full-relay", services=NODE_NETWORK, ) non_avapeers.append(peer) inbound_avapeers = [ node.add_p2p_connection( AvaP2PInterface()) for _ in range(4)] outbound_avapeers = [] for i in range(4): peer = P2PInterface() node.add_outbound_p2p_connection( peer, p2p_idx=16 + i, connection_type="avalanche", services=NODE_NETWORK | NODE_AVALANCHE, ) outbound_avapeers.append(peer) self.wait_until( lambda: all([p.last_message.get("getavaproofs") for p in outbound_avapeers])) 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]) 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() 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)}" 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') self.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 received_avaproofs(peer): with p2p_lock: return peer.last_message.get("avaproofs") def send_getavaproof_check_shortid_len(peer, expected_len): peer.send_message(msg_getavaproofs()) self.wait_until(lambda: received_avaproofs(peer)) avaproofs = received_avaproofs(peer) assert_equal(len(avaproofs.shortids), expected_len) # Initially the node has 0 peer assert_equal(len(get_proof_ids(node)), 0) peer = node.add_p2p_connection(AvaP2PInterface()) send_getavaproof_check_shortid_len(peer, 0) # Add some proofs sending_peer = node.add_p2p_connection(AvaP2PInterface()) for _ in range(50): _, proof = gen_proof(node) sending_peer.send_avaproof(proof) wait_for_proof(node, f"{proof.proofid:0{64}x}") proofids = get_proof_ids(node) assert_equal(len(proofids), 50) receiving_peer = node.add_p2p_connection(AvaP2PInterface()) send_getavaproof_check_shortid_len(receiving_peer, len(proofids)) avaproofs = received_avaproofs(receiving_peer) expected_shortids = [ - siphash256( + calculate_shortid( avaproofs.key0, avaproofs.key1, - proofid) & 0x0000ffffffffffff for proofid in sorted(proofids)] + 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 run_test(self): self.test_send_outbound_getavaproofs() self.test_send_manual_getavaproofs() self.test_respond_getavaproofs() if __name__ == '__main__': CompactProofsTest().main() diff --git a/test/functional/abc_p2p_proof_inventory.py b/test/functional/abc_p2p_proof_inventory.py index f47a9db68..b4c9b21a2 100644 --- a/test/functional/abc_p2p_proof_inventory.py +++ b/test/functional/abc_p2p_proof_inventory.py @@ -1,351 +1,351 @@ #!/usr/bin/env python3 # Copyright (c) 2021 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 time from test_framework.address import ADDRESS_ECREG_UNSPENDABLE from test_framework.avatools import ( AvaP2PInterface, avalanche_proof_from_hex, gen_proof, get_proof_ids, wait_for_proof, ) from test_framework.key import ECKey from test_framework.messages import ( MSG_AVA_PROOF, MSG_TYPE_MASK, CInv, + calculate_shortid, msg_avaproof, msg_getavaproofs, msg_getdata, ) from test_framework.p2p import P2PInterface, p2p_lock -from test_framework.siphash import siphash256 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than from test_framework.wallet_util import bytes_to_wif # Broadcast reattempt occurs every 10 to 15 minutes MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # Delay to allow the node to respond to getdata requests UNCONDITIONAL_RELAY_DELAY = 2 * 60 class ProofInvStoreP2PInterface(P2PInterface): def __init__(self): super().__init__() self.proof_invs_counter = 0 def on_inv(self, message): for i in message.inv: if i.type & MSG_TYPE_MASK == MSG_AVA_PROOF: self.proof_invs_counter += 1 class ProofInventoryTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 5 self.extra_args = [[ '-enableavalanche=1', '-avacooldown=0', '-whitelist=noban@127.0.0.1', ]] * self.num_nodes def test_send_proof_inv(self): self.log.info("Test sending a proof to our peers") node = self.nodes[0] for i in range(10): node.add_p2p_connection(ProofInvStoreP2PInterface()) _, proof = gen_proof(node) assert node.sendavalancheproof(proof.serialize().hex()) def proof_inv_found(peer): with p2p_lock: return peer.last_message.get( "inv") and peer.last_message["inv"].inv[-1].hash == proof.proofid self.wait_until(lambda: all(proof_inv_found(i) for i in node.p2ps)) self.log.info("Test that we don't send the same inv several times") extra_peer = ProofInvStoreP2PInterface() node.add_p2p_connection(extra_peer) # Send the same proof one more time node.sendavalancheproof(proof.serialize().hex()) # Our new extra peer should receive it but not the others self.wait_until(lambda: proof_inv_found(extra_peer)) assert all(p.proof_invs_counter == 1 for p in node.p2ps) # Send the proof again and force the send loop to be processed for peer in node.p2ps: node.sendavalancheproof(proof.serialize().hex()) peer.sync_with_ping() assert all(p.proof_invs_counter == 1 for p in node.p2ps) def test_receive_proof(self): self.log.info("Test a peer is created on proof reception") node = self.nodes[0] _, proof = gen_proof(node) peer = node.add_p2p_connection(P2PInterface()) msg = msg_avaproof() msg.proof = proof peer.send_message(msg) self.wait_until(lambda: proof.proofid in get_proof_ids(node)) self.log.info("Test receiving a proof with missing utxo is orphaned") privkey = ECKey() privkey.generate() orphan_hex = node.buildavalancheproof( 42, 2000000000, bytes_to_wif(privkey.get_bytes()), [{ 'txid': '0' * 64, 'vout': 0, 'amount': 10e6, 'height': 42, 'iscoinbase': False, 'privatekey': bytes_to_wif(privkey.get_bytes()), }] ) orphan = avalanche_proof_from_hex(orphan_hex) orphan_proofid = "{:064x}".format(orphan.proofid) msg = msg_avaproof() msg.proof = orphan peer.send_message(msg) wait_for_proof(node, orphan_proofid, expect_orphan=True) def test_ban_invalid_proof(self): node = self.nodes[0] _, bad_proof = gen_proof(node) bad_proof.stakes = [] self.restart_node(0, ['-enableavalanche=1']) peer = node.add_p2p_connection(P2PInterface()) msg = msg_avaproof() msg.proof = bad_proof with node.assert_debug_log([ 'Misbehaving', 'invalid-proof', ]): peer.send_message(msg) peer.wait_for_disconnect() def test_proof_relay(self): # This test makes no sense with a single node ! assert_greater_than(self.num_nodes, 1) def restart_nodes_with_proof(nodes=self.nodes): proofids = set() for i, node in enumerate(nodes): privkey, proof = gen_proof(node) proofids.add(proof.proofid) self.restart_node(node.index, self.extra_args[node.index] + [ "-avaproof={}".format(proof.serialize().hex()), "-avamasterkey={}".format(bytes_to_wif(privkey.get_bytes())) ]) # Connect a block to make the proof be added to our pool node.generate(1) self.wait_until(lambda: proof.proofid in get_proof_ids(node)) [self.connect_nodes(node.index, j) for j in range(node.index)] return proofids proofids = restart_nodes_with_proof(self.nodes) self.log.info("Nodes should eventually get the proof from their peer") self.sync_proofs() for node in self.nodes: assert_equal(set(get_proof_ids(node)), proofids) def test_manually_sent_proof(self): node0 = self.nodes[0] _, proof = gen_proof(node0) self.log.info( "Send a proof via RPC and check all the nodes download it") node0.sendavalancheproof(proof.serialize().hex()) self.sync_proofs() def test_respond_getavaproofs(self): self.log.info("Check the node responds to getavaproofs messages") self.restart_node(0) node = self.nodes[0] def received_avaproofs(peer): with p2p_lock: return peer.last_message.get("avaproofs") def send_getavaproof_check_shortid_len(peer, expected_len): peer.send_message(msg_getavaproofs()) self.wait_until(lambda: received_avaproofs(peer)) avaproofs = received_avaproofs(peer) assert_equal(len(avaproofs.shortids), expected_len) # Initially the node has 0 peer assert_equal(len(get_proof_ids(node)), 0) peer = node.add_p2p_connection(AvaP2PInterface()) send_getavaproof_check_shortid_len(peer, 0) # Add some proofs sending_peer = node.add_p2p_connection(AvaP2PInterface()) for _ in range(50): _, proof = gen_proof(node) sending_peer.send_avaproof(proof) wait_for_proof(node, f"{proof.proofid:0{64}x}") proofids = get_proof_ids(node) assert_equal(len(proofids), 50) receiving_peer = node.add_p2p_connection(AvaP2PInterface()) send_getavaproof_check_shortid_len(receiving_peer, len(proofids)) avaproofs = received_avaproofs(receiving_peer) expected_shortids = [ - siphash256( + calculate_shortid( avaproofs.key0, avaproofs.key1, - proofid) & 0x0000ffffffffffff for proofid in sorted(proofids)] + 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_unbroadcast(self): self.log.info("Test broadcasting proofs") node = self.nodes[0] # Disconnect the other nodes/peers, or they will request the proof and # invalidate the test [n.stop_node() for n in self.nodes[1:]] node.disconnect_p2ps() def add_peers(count): peers = [] for i in range(count): peer = node.add_p2p_connection(ProofInvStoreP2PInterface()) peer.wait_for_verack() peers.append(peer) return peers _, proof = gen_proof(node) proofid_hex = "{:064x}".format(proof.proofid) # Broadcast the proof peers = add_peers(3) assert node.sendavalancheproof(proof.serialize().hex()) wait_for_proof(node, proofid_hex) def proof_inv_received(peers): with p2p_lock: return all(p.last_message.get( "inv") and p.last_message["inv"].inv[-1].hash == proof.proofid for p in peers) self.wait_until(lambda: proof_inv_received(peers)) # If no peer request the proof for download, the node should reattempt # broadcasting to all new peers after 10 to 15 minutes. peers = add_peers(3) node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY + 1) peers[-1].sync_with_ping() self.wait_until(lambda: proof_inv_received(peers)) # If at least one peer requests the proof, there is no more attempt to # broadcast it node.setmocktime(int(time.time()) + UNCONDITIONAL_RELAY_DELAY) msg = msg_getdata([CInv(t=MSG_AVA_PROOF, h=proof.proofid)]) peers[-1].send_message(msg) # Give enough time for the node to broadcast the proof again peers = add_peers(3) node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY + 1) peers[-1].sync_with_ping() assert not proof_inv_received(peers) self.log.info( "Proofs that become invalid should no longer be broadcasted") # Restart and add connect a new set of peers self.restart_node(0) # Broadcast the proof peers = add_peers(3) assert node.sendavalancheproof(proof.serialize().hex()) self.wait_until(lambda: proof_inv_received(peers)) # Sanity check our node knows the proof, and it is valid wait_for_proof(node, proofid_hex, expect_orphan=False) # Mature the utxo then spend it node.generate(100) utxo = proof.stakes[0].stake.utxo raw_tx = node.createrawtransaction( inputs=[{ # coinbase "txid": "{:064x}".format(utxo.hash), "vout": utxo.n }], outputs={ADDRESS_ECREG_UNSPENDABLE: 25_000_000 - 250.00}, ) signed_tx = node.signrawtransactionwithkey( hexstring=raw_tx, privkeys=[node.get_deterministic_priv_key().key], ) node.sendrawtransaction(signed_tx['hex']) # Mine the tx in a block node.generate(1) # Wait for the proof to be orphaned self.wait_until(lambda: node.getrawavalancheproof( proofid_hex)["orphan"] is True) # It should no longer be broadcasted peers = add_peers(3) node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY + 1) peers[-1].sync_with_ping() assert not proof_inv_received(peers) def run_test(self): self.test_send_proof_inv() self.test_receive_proof() self.test_proof_relay() self.test_manually_sent_proof() self.test_respond_getavaproofs() # Run these tests last because they need to disconnect the nodes self.test_unbroadcast() self.test_ban_invalid_proof() if __name__ == '__main__': ProofInventoryTest().main()