diff --git a/test/functional/abc_p2p_avalanche_peer_discovery.py b/test/functional/abc_p2p_avalanche_peer_discovery.py new file mode 100755 --- /dev/null +++ b/test/functional/abc_p2p_avalanche_peer_discovery.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020-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 the peer discovery behavior of avalanche nodes. + +This includes tests for the service flag, avahello handshake and +proof exchange. +""" + +import time + +from test_framework.avatools import ( + get_ava_p2p_interface, + create_coinbase_stakes, + get_proof_ids, +) +from test_framework.key import ( + bytes_to_wif, + ECKey, + ECPubKey, +) +from test_framework.p2p import p2p_lock +from test_framework.messages import ( + AvalancheProof, + CInv, + FromHex, + MSG_AVA_PROOF, + msg_getdata, + NODE_AVALANCHE, + NODE_NETWORK, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + wait_until, +) + +UNCONDITIONAL_RELAY_DELAY = 2 * 60 + + +class AvalancheTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-enableavalanche=1']] + self.supports_cli = False + + def run_test(self): + node = self.nodes[0] + + # duplicate the deterministic sig test from src/test/key_tests.cpp + privkey = ECKey() + privkey.set(bytes.fromhex( + "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) + pubkey = privkey.get_pubkey() + + self.log.info( + "Check the node is signalling the avalanche service bit only if there is a proof.") + assert_equal( + int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, + 0) + + # Create stakes by mining blocks + addrkey0 = node.get_deterministic_priv_key() + blockhashes = node.generatetoaddress(2, addrkey0.address) + stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key) + + proof_sequence = 11 + proof_expiration = 12 + proof = node.buildavalancheproof( + proof_sequence, proof_expiration, pubkey.get_bytes().hex(), + stakes) + + # Restart the node + self.restart_node(0, self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + ]) + + assert_equal( + int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, + NODE_AVALANCHE) + + self.log.info("Test the avahello signature (node -> P2PInterface)") + good_interface = get_ava_p2p_interface(node) + avahello = good_interface.wait_for_avahello().hello + + avakey = ECPubKey() + avakey.set(bytes.fromhex(node.getavalanchekey())) + assert avakey.verify_schnorr( + avahello.sig, avahello.get_sighash(good_interface)) + + stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key) + interface_proof_hex = node.buildavalancheproof( + proof_sequence, proof_expiration, pubkey.get_bytes().hex(), + stakes) + limited_id = FromHex( + AvalancheProof(), + interface_proof_hex).limited_proofid + + # delegate + delegated_key = ECKey() + delegated_key.generate() + interface_delegation_hex = node.delegateavalancheproof( + f"{limited_id:0{64}x}", + bytes_to_wif(privkey.get_bytes()), + delegated_key.get_pubkey().get_bytes().hex(), + None) + + self.log.info("Test that wrong avahello signature causes a ban") + bad_interface = get_ava_p2p_interface(node) + wrong_key = ECKey() + wrong_key.generate() + with self.nodes[0].assert_debug_log( + ["Misbehaving", + "peer=1 (0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avahello-signature"]): + bad_interface.send_avahello(interface_delegation_hex, wrong_key) + bad_interface.wait_for_disconnect() + + self.log.info( + 'Check that receiving a valid avahello triggers a proof getdata request') + proofid = good_interface.send_avahello( + interface_delegation_hex, delegated_key) + + def getdata_found(): + with p2p_lock: + return good_interface.last_message.get( + "getdata") and good_interface.last_message["getdata"].inv[-1].hash == proofid + wait_until(getdata_found) + + self.log.info('Check that we can download the proof from our peer') + + node_proofid = FromHex(AvalancheProof(), proof).proofid + + def wait_for_proof_validation(): + # Connect some blocks to trigger the proof verification + node.generate(2) + wait_until(lambda: node_proofid in get_proof_ids(node)) + + wait_for_proof_validation() + + getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)]) + + self.log.info( + "Proof has been inv'ed recently, check it can be requested") + good_interface.send_message(getdata) + + def proof_received(peer): + with p2p_lock: + return peer.last_message.get( + "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid + wait_until(lambda: proof_received(good_interface)) + + # Restart the node + self.restart_node(0, self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + ]) + wait_for_proof_validation() + + self.log.info( + "The proof has not been announced, it cannot be requested") + peer = get_ava_p2p_interface(node, services=NODE_NETWORK) + peer.send_message(getdata) + + # Give enough time for the node to answer. Since we cannot check for a + # non-event this is the best we can do + time.sleep(2) + assert not proof_received(peer) + + self.log.info("The proof is known for long enough to be requested") + current_time = int(time.time()) + node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY) + + peer.send_message(getdata) + wait_until(lambda: proof_received(peer)) + + +if __name__ == '__main__': + AvalancheTest().main() diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche_voting.py rename from test/functional/abc_p2p_avalanche.py rename to test/functional/abc_p2p_avalanche_voting.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche_voting.py @@ -4,29 +4,16 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the resolution of forks via avalanche.""" import random -import time from test_framework.avatools import ( get_ava_p2p_interface, create_coinbase_stakes, - get_proof_ids, ) from test_framework.key import ( - bytes_to_wif, ECKey, ECPubKey, ) -from test_framework.p2p import p2p_lock -from test_framework.messages import ( - AvalancheProof, - AvalancheVote, - CInv, - FromHex, - MSG_AVA_PROOF, - msg_getdata, - NODE_AVALANCHE, - NODE_NETWORK, -) +from test_framework.messages import AvalancheVote from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -43,8 +30,6 @@ QUORUM_NODE_COUNT = 16 -UNCONDITIONAL_RELAY_DELAY = 2 * 60 - class AvalancheTest(BitcoinTestFramework): def set_test_params(self): @@ -263,115 +248,6 @@ poll_node.send_avaresponse( round=2**32 - 1, votes=[], privkey=privkey) - self.log.info( - "Check the node is signalling the avalanche service bit only if there is a proof.") - assert_equal( - int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, - 0) - - # Restart the node - self.restart_node(0, self.extra_args[0] + [ - "-avaproof={}".format(proof), - "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", - ]) - - assert_equal( - int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, - NODE_AVALANCHE) - - self.log.info("Test the avahello signature (node -> P2PInterface)") - good_interface = get_ava_p2p_interface(node) - avahello = good_interface.wait_for_avahello().hello - - avakey.set(bytes.fromhex(node.getavalanchekey())) - assert avakey.verify_schnorr( - avahello.sig, avahello.get_sighash(good_interface)) - - stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key) - interface_proof_hex = node.buildavalancheproof( - proof_sequence, proof_expiration, pubkey.get_bytes().hex(), - stakes) - limited_id = FromHex( - AvalancheProof(), - interface_proof_hex).limited_proofid - - # delegate - delegated_key = ECKey() - delegated_key.generate() - interface_delegation_hex = node.delegateavalancheproof( - f"{limited_id:0{64}x}", - bytes_to_wif(privkey.get_bytes()), - delegated_key.get_pubkey().get_bytes().hex(), - None) - - self.log.info("Test that wrong avahello signature causes a ban") - bad_interface = get_ava_p2p_interface(node) - wrong_key = ECKey() - wrong_key.generate() - with self.nodes[0].assert_debug_log( - ["Misbehaving", - "peer=1 (0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avahello-signature"]): - bad_interface.send_avahello(interface_delegation_hex, wrong_key) - bad_interface.wait_for_disconnect() - - self.log.info( - 'Check that receiving a valid avahello triggers a proof getdata request') - proofid = good_interface.send_avahello( - interface_delegation_hex, delegated_key) - - def getdata_found(): - with p2p_lock: - return good_interface.last_message.get( - "getdata") and good_interface.last_message["getdata"].inv[-1].hash == proofid - wait_until(getdata_found) - - self.log.info('Check that we can download the proof from our peer') - - node_proofid = FromHex(AvalancheProof(), proof).proofid - - def wait_for_proof_validation(): - # Connect some blocks to trigger the proof verification - node.generate(2) - wait_until(lambda: node_proofid in get_proof_ids(node)) - - wait_for_proof_validation() - - getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)]) - - self.log.info( - "Proof has been inv'ed recently, check it can be requested") - good_interface.send_message(getdata) - - def proof_received(peer): - with p2p_lock: - return peer.last_message.get( - "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid - wait_until(lambda: proof_received(good_interface)) - - # Restart the node - self.restart_node(0, self.extra_args[0] + [ - "-avaproof={}".format(proof), - "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", - ]) - wait_for_proof_validation() - - self.log.info( - "The proof has not been announced, it cannot be requested") - peer = get_ava_p2p_interface(node, services=NODE_NETWORK) - peer.send_message(getdata) - - # Give enough time for the node to answer. Since we cannot check for a - # non-event this is the best we can do - time.sleep(2) - assert not proof_received(peer) - - self.log.info("The proof is known for long enough to be requested") - current_time = int(time.time()) - node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY) - - peer.send_message(getdata) - wait_until(lambda: proof_received(peer)) - if __name__ == '__main__': AvalancheTest().main()