diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -1,33 +1,23 @@ #!/usr/bin/env python3 -# Copyright (c) 2018 The Bitcoin developers +# 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 resolution of forks via avalanche.""" +"""Test the resolution of forks via avalanche""" import random -from typing import List, Dict from test_framework.key import ( ECKey, ECPubKey, ) -from test_framework.mininode import P2PInterface, mininode_lock from test_framework.messages import ( - AvalancheResponse, AvalancheVote, - CInv, - MSG_AVALANCHE_PROOF, - msg_avapoll, - msg_avahello, - msg_getdata, - msg_tcpavaresponse, NODE_AVALANCHE, NODE_NETWORK, - TCPAvalancheResponse, ) +from test_framework.mininode import P2PAvalancheTestNode from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - assert_raises_rpc_error, wait_until, ) @@ -42,120 +32,6 @@ BLOCK_PENDING = -3 QUORUM_NODE_COUNT = 16 -DUMMY_PROOFID = 1337 - - -class TestNode(P2PInterface): - - def __init__(self): - self.round = 0 - self.avahello = None - self.avaresponses = [] - self.avapolls = [] - self.avaproof = None - super().__init__() - - def peer_connect(self, *args, **kwargs): - create_conn = super().peer_connect(*args, **kwargs) - - # Save the nonce and extra entropy so they can be reused later. - self.local_nonce = self.on_connection_send_msg.nNonce - self.local_extra_entropy = self.on_connection_send_msg.nExtraEntropy - - return create_conn - - def on_version(self, message): - super().on_version(message) - - # Save the nonce and extra entropy so they can be reused later. - self.remote_nonce = message.nNonce - self.remote_extra_entropy = message.nExtraEntropy - - def on_avaresponse(self, message): - with mininode_lock: - self.avaresponses.append(message.response) - - def on_avapoll(self, message): - with mininode_lock: - self.avapolls.append(message.poll) - - def on_avahello(self, message): - with mininode_lock: - assert(self.avahello is None) - self.avahello = message - - def send_avaresponse(self, round, votes, privkey): - response = AvalancheResponse(round, 0, votes) - sig = privkey.sign_schnorr(response.get_hash()) - msg = msg_tcpavaresponse() - msg.response = TCPAvalancheResponse(response, sig) - self.send_message(msg) - - def wait_for_avaresponse(self, timeout=5): - wait_until( - lambda: len(self.avaresponses) > 0, - timeout=timeout, - lock=mininode_lock) - - with mininode_lock: - return self.avaresponses.pop(0) - - def send_poll(self, hashes): - msg = msg_avapoll() - msg.poll.round = self.round - self.round += 1 - for h in hashes: - msg.poll.invs.append(CInv(2, h)) - self.send_message(msg) - - def get_avapoll_if_available(self): - with mininode_lock: - return self.avapolls.pop(0) if len(self.avapolls) > 0 else None - - def wait_for_avahello(self, timeout=5): - wait_until( - lambda: self.avahello is not None, - timeout=timeout, - lock=mininode_lock) - - with mininode_lock: - return self.avahello - - def send_avahello(self): - msg = msg_avahello() - msg.hello.delegation.proofid = DUMMY_PROOFID - self.send_message(msg) - - def send_getdata(self, inv: List[CInv]): - msg = msg_getdata() - msg.inv = inv - self.send_message(msg) - - def on_avaproof(self, message): - with mininode_lock: - assert(self.avaproof is None) - self.avaproof = message - - def wait_for_avaproof(self, timeout=10): - wait_until( - lambda: self.avaproof is not None, - timeout=timeout, - lock=mininode_lock) - - with mininode_lock: - return self.avaproof - - -def get_stakes(coinbases: List[Dict], - priv_key: str) -> List[Dict]: - return [{ - 'txid': coinbase['txid'], - 'vout': coinbase['n'], - 'amount': coinbase['value'], - 'height': coinbase['height'], - 'iscoinbase': True, - 'privatekey': priv_key, - } for coinbase in coinbases] class AvalancheTest(BitcoinTestFramework): @@ -171,14 +47,9 @@ def run_test(self): node = self.nodes[0] - self.log.info("Check the node is signalling the avalanche service.") - assert_equal( - int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, - NODE_AVALANCHE) - # Build a fake quorum of nodes. def get_node(): - n = TestNode() + n = P2PAvalancheTestNode() node.add_p2p_connection( n, services=NODE_NETWORK | NODE_AVALANCHE) n.wait_for_verack() @@ -289,12 +160,12 @@ privkey = ECKey() privkey.set(bytes.fromhex( "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) - pubkey = privkey.get_pubkey() + proof_master = privkey.get_pubkey().get_bytes().hex() proof_sequence = 11 proof_expiration = 12 proof = node.buildavalancheproof( - proof_sequence, proof_expiration, pubkey.get_bytes().hex(), + proof_sequence, proof_expiration, proof_master, [{ 'txid': coinbases[0]['txid'], 'vout': coinbases[0]['n'], @@ -307,7 +178,7 @@ # Activate the quorum. for n in quorum: success = node.addavalanchenode( - n.nodeid, pubkey.get_bytes().hex(), proof) + n.nodeid, proof_master, proof) assert success is True self.log.info("Testing getavalanchepeerinfo...") @@ -321,7 +192,7 @@ list(range(1, QUORUM_NODE_COUNT + 1))) assert_equal(avapeerinfo[0]["sequence"], proof_sequence) assert_equal(avapeerinfo[0]["expiration"], proof_expiration) - assert_equal(avapeerinfo[0]["master"], pubkey.get_bytes().hex()) + assert_equal(avapeerinfo[0]["master"], proof_master) assert_equal(avapeerinfo[0]["proof"], proof) assert_equal(len(avapeerinfo[0]["stakes"]), 1) assert_equal(avapeerinfo[0]["stakes"][0]["txid"], coinbases[0]['txid']) @@ -403,130 +274,6 @@ wait_until(has_parked_new_tip, timeout=15) assert_equal(node.getbestblockhash(), fork_tip) - # Restart the node - minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1 - self.restart_node(0, self.extra_args[0] + [ - "-avaproof={}".format(proof), - "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", - "-minimumchainwork=0x{:x}".format(minchainwork), - ]) - self.log.info( - "The proof verification should be delayed until IBD is complete") - assert node.getblockchaininfo()["initialblockdownload"] is True - # Our proof cannot be verified during IBD, so we should have no peer - assert not node.getavalanchepeerinfo() - # Mining a few more blocks should cause us to leave IBD - node.generate(2) - # Our proof is now verified and our node is added as a peer - assert node.getblockchaininfo()["initialblockdownload"] is False - wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5) - - # Rebuild the quorum - self.log.info("Test the avahello signature") - quorum = get_quorum() - poll_node = quorum[0] - - # Check the handshake sequence - self.log.info("Receive AVAHELLO, request the corresponding proof") - avahello = poll_node.wait_for_avahello().hello - - avakey.set(bytes.fromhex(node.getavalanchekey())) - assert avakey.verify_schnorr( - avahello.sig, avahello.get_sighash(poll_node)) - - poll_node.send_getdata([CInv(MSG_AVALANCHE_PROOF, - avahello.delegation.proofid)]) - poll_node.wait_for_avaproof() - # TODO: deserialize proof and check it in more details - - self.log.info( - "Send an AVAHELLO to the node, check that it ask for our prooof") - poll_node.send_avahello() - poll_node.wait_for_getdata([DUMMY_PROOFID]) - # TODO: reply with a new AVAPROOF message and verify that poll node is - # added to node's avalanche peers - - # Check the maximum number of stakes policy - blocks = node.generatetoaddress(AVALANCHE_MAX_PROOF_STAKES + 1, - addrkey0.address) - - too_many_coinbases = [get_coinbase(h) for h in blocks] - too_many_stakes = get_stakes(too_many_coinbases, addrkey0.key) - - self.log.info( - "A proof using the maximum number of stakes is accepted...") - maximum_stakes = get_stakes(too_many_coinbases[:-1], - addrkey0.key) - good_proof = node.buildavalancheproof( - proof_sequence, proof_expiration, - pubkey.get_bytes().hex(), maximum_stakes) - node.addavalanchenode( - get_node().nodeid, pubkey.get_bytes().hex(), good_proof) - - self.log.info("A proof using too many stakes should be rejected...") - bad_proof = node.buildavalancheproof( - proof_sequence, proof_expiration, - pubkey.get_bytes().hex(), too_many_stakes) - assert_raises_rpc_error(-32602, "Avalanche proof has too many UTXOs", - node.addavalanchenode, - get_node().nodeid, pubkey.get_bytes().hex(), - bad_proof) - - self.log.info("Bad proof should be rejected at startup") - - no_stake = node.buildavalancheproof( - proof_sequence, proof_expiration, pubkey.get_bytes().hex(), []) - - dust = node.buildavalancheproof( - proof_sequence, proof_expiration, pubkey.get_bytes().hex(), - [{ - 'txid': coinbases[0]['txid'], - 'vout': coinbases[0]['n'], - 'amount': '0', - 'height': coinbases[0]['height'], - 'iscoinbase': True, - 'privatekey': addrkey0.key, - }]) - - duplicate_stake = node.buildavalancheproof( - proof_sequence, proof_expiration, pubkey.get_bytes().hex(), - [{ - 'txid': coinbases[0]['txid'], - 'vout': coinbases[0]['n'], - 'amount': coinbases[0]['value'], - 'height': coinbases[0]['height'], - 'iscoinbase': True, - 'privatekey': addrkey0.key, - }] * 2) - - bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348" - "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085" - "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20" - "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3" - "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0" - "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974" - "1321b91a79b82d1c2cfd47793261e4ba003cf5") - - self.stop_node(0) - - def check_proof_init_error(proof, message): - node.assert_start_raises_init_error( - self.extra_args[0] + [ - "-avaproof={}".format(proof), - "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", - ], - expected_msg="Error: " + message, - ) - - check_proof_init_error(no_stake, - "the avalanche proof has no stake") - check_proof_init_error(dust, - "the avalanche proof stake is too low") - check_proof_init_error(duplicate_stake, - "the avalanche proof has duplicated stake") - check_proof_init_error(bad_sig, - "the avalanche proof has invalid stake signatures") - if __name__ == '__main__': AvalancheTest().main() diff --git a/test/functional/abc_p2p_avalanche_peer_discovery.py b/test/functional/abc_p2p_avalanche_peer_discovery.py new file mode 100644 --- /dev/null +++ b/test/functional/abc_p2p_avalanche_peer_discovery.py @@ -0,0 +1,102 @@ +#!/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 avalanche peer discovery""" +from test_framework.key import ( + ECKey, + ECPubKey, +) +from test_framework.messages import ( + CInv, + MSG_AVALANCHE_PROOF, + NODE_AVALANCHE, + NODE_NETWORK, +) +from test_framework.mininode import P2PAvalancheTestNode +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class AvalancheTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-enableavalanche=1', '-avacooldown=0']] + + def run_test(self): + node = self.nodes[0] + + # Get the key so we can verify signatures. + avakey = ECPubKey() + avakey.set(bytes.fromhex(node.getavalanchekey())) + + self.log.info("Check the node is signalling the avalanche service.") + assert_equal( + int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, + NODE_AVALANCHE) + + # Generate a block to have a stake for avalanche + addrkey = node.get_deterministic_priv_key() + coinbase_address = addrkey.address + coinbase_key = addrkey.key + blockhash = node.generatetoaddress(1, coinbase_address)[0] + block = node.getblock(blockhash, 2) + + # Make a proof + privkey = ECKey() + privkey.set(bytes.fromhex( + "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) + proof_master = privkey.get_pubkey().get_bytes().hex() + + proof_sequence = 11 + proof_expiration = 12 + proof = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + [{ + 'txid': block['tx'][0]['txid'], + 'vout': 0, + 'amount': block['tx'][0]['vout'][0]['value'], + 'height': block['height'], + 'iscoinbase': True, + 'privatekey': coinbase_key, + }]) + + # Restart the main node with an avaproof specified + self.restart_node(0, self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + ]) + + # Create a peer + ava_mininode = P2PAvalancheTestNode() + node.add_p2p_connection( + ava_mininode, services=NODE_NETWORK | NODE_AVALANCHE) + ava_mininode.wait_for_verack() + + # Check the handshake sequence + self.log.info("Test avahello sequence node -> ava_mininode") + avahello = ava_mininode.wait_for_avahello().hello + avakey.set(bytes.fromhex(node.getavalanchekey())) + assert avakey.verify_schnorr( + avahello.sig, avahello.get_sighash(ava_mininode)) + + ava_mininode.send_getdata([CInv(MSG_AVALANCHE_PROOF, + avahello.delegation.proofid)]) + avaproof = ava_mininode.wait_for_avaproof().proof + assert_equal(proof, avaproof.serialize().hex()) + assert_equal(avaproof.proofid, avahello.delegation.proofid) + + self.log.info( + "Test avahello sequence ava_mininode -> node") + # This is testing only the first couple of steps (for now) + # TODO: make a proper delegation and a valid signature for avahello, + # reply with a valid avaproof, test that the node adds us. + ava_mininode.send_avahello() + ava_mininode.wait_for_getdata([P2PAvalancheTestNode.DUMMY_PROOFID]) + + +if __name__ == '__main__': + AvalancheTest().main() diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py new file mode 100644 --- /dev/null +++ b/test/functional/abc_rpc_avalancheproof.py @@ -0,0 +1,184 @@ +#!/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 building avalanche proofs and using them to add avalanche peers.""" +from typing import List, Dict + +from test_framework.key import ECKey + +from test_framework.mininode import P2PInterface + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_raises_rpc_error, + wait_until, +) + +AVALANCHE_MAX_PROOF_STAKES = 1000 + + +def get_stakes(coinbases: List[Dict], + priv_key: str) -> List[Dict]: + return [{ + 'txid': coinbase['txid'], + 'vout': coinbase['n'], + 'amount': coinbase['value'], + 'height': coinbase['height'], + 'iscoinbase': True, + 'privatekey': priv_key, + } for coinbase in coinbases] + + +class AvalancheTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-enableavalanche=1', '-avacooldown=0'], ] + + def run_test(self): + node = self.nodes[0] + + def get_interface_node(): + """Return a P2P interface node signaling the avalanche flag.""" + n = P2PInterface() + node.add_p2p_connection(n) + n.wait_for_verack() + n.nodeid = node.getpeerinfo()[-1]['id'] + return n + + def get_coinbase(h): + b = node.getblock(h, 2) + return { + 'height': b['height'], + 'txid': b['tx'][0]['txid'], + 'n': 0, + 'value': b['tx'][0]['vout'][0]['value'], + } + + addrkey0 = node.get_deterministic_priv_key() + blocks = node.generatetoaddress(100, addrkey0.address) + coinbases = [get_coinbase(h) for h in blocks] + + self.log.info( + "Make build a valid proof and restart the node to use it") + privkey = ECKey() + privkey.set(bytes.fromhex( + "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) + + proof_master = privkey.get_pubkey().get_bytes().hex() + proof_sequence = 11 + proof_expiration = 12 + proof = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + [{ + 'txid': coinbases[0]['txid'], + 'vout': coinbases[0]['n'], + 'amount': coinbases[0]['value'], + 'height': coinbases[0]['height'], + 'iscoinbase': True, + 'privatekey': addrkey0.key, + }]) + + # Restart the node, making sure it is initially in IBD mode + minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1 + self.restart_node(0, self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + "-minimumchainwork=0x{:x}".format(minchainwork), + ]) + + self.log.info( + "The proof verification should be delayed until IBD is complete") + assert node.getblockchaininfo()["initialblockdownload"] is True + # Our proof cannot be verified during IBD, so we should have no peer + assert not node.getavalanchepeerinfo() + # Mining a few more blocks should cause us to leave IBD + node.generate(2) + # Our proof is now verified and our node is added as a peer + assert node.getblockchaininfo()["initialblockdownload"] is False + wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5) + + self.log.info( + "A proof using the maximum number of stakes is accepted...") + blocks = node.generatetoaddress(AVALANCHE_MAX_PROOF_STAKES + 1, + addrkey0.address) + + too_many_coinbases = [get_coinbase(h) for h in blocks] + too_many_stakes = get_stakes(too_many_coinbases, addrkey0.key) + + maximum_stakes = get_stakes(too_many_coinbases[:-1], + addrkey0.key) + good_proof = node.buildavalancheproof( + proof_sequence, proof_expiration, + proof_master, maximum_stakes) + node.addavalanchenode( + get_interface_node().nodeid, proof_master, good_proof) + + self.log.info("A proof using too many stakes should be rejected...") + bad_proof = node.buildavalancheproof( + proof_sequence, proof_expiration, + proof_master, too_many_stakes) + assert_raises_rpc_error(-32602, "Avalanche proof has too many UTXOs", + node.addavalanchenode, + get_interface_node().nodeid, proof_master, + bad_proof) + + # Test invalid proofs + self.log.info("Bad proof should be rejected at startup") + no_stake = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, []) + + dust = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + [{ + 'txid': coinbases[0]['txid'], + 'vout': coinbases[0]['n'], + 'amount': '0', + 'height': coinbases[0]['height'], + 'iscoinbase': True, + 'privatekey': addrkey0.key, + }]) + + duplicate_stake = node.buildavalancheproof( + proof_sequence, proof_expiration, proof_master, + [{ + 'txid': coinbases[0]['txid'], + 'vout': coinbases[0]['n'], + 'amount': coinbases[0]['value'], + 'height': coinbases[0]['height'], + 'iscoinbase': True, + 'privatekey': addrkey0.key, + }] * 2) + + bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348" + "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085" + "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20" + "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3" + "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0" + "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974" + "1321b91a79b82d1c2cfd47793261e4ba003cf5") + + self.stop_node(0) + + def check_proof_init_error(proof, message): + node.assert_start_raises_init_error( + self.extra_args[0] + [ + "-avaproof={}".format(proof), + "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + ], + expected_msg="Error: " + message, + ) + + check_proof_init_error(no_stake, + "the avalanche proof has no stake") + check_proof_init_error(dust, + "the avalanche proof stake is too low") + check_proof_init_error(duplicate_stake, + "the avalanche proof has duplicated stake") + check_proof_init_error(bad_sig, + "the avalanche proof has invalid stake signatures") + + +if __name__ == '__main__': + AvalancheTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -854,23 +854,115 @@ self.round, repr(self.invs)) -# TODO: implement the Proof with all its attributes -class AvalancheProof: - __slots__ = ("blob") +class AvalancheStake: + def __init__(self, utxo=None, amount=0, height=0, + pubkey=b"", is_coinbase=False): + self.utxo: COutPoint = utxo or COutPoint() + self.amount: int = amount + """Amount in satoshis (int64)""" + self.height: int = height + """Block height containing this utxo (uint32)""" + self.pubkey: bytes = pubkey + """Public key""" + + self.is_coinbase: bool = is_coinbase + + def deserialize(self, f): + self.utxo = COutPoint() + self.utxo.deserialize(f) + self.amount = struct.unpack("> 1 + self.pubkey = deser_string(f) + + def serialize(self) -> bytes: + r = self.utxo.serialize() + height_ser = self.height << 1 | int(self.is_coinbase) + r += struct.pack(' bytes: + """Return the bitcoin hash of the concatenation of proofid + and the serialized stake.""" + return hash256(proofid + self.serialize()) + + def __repr__(self): + return f"AvalancheStake(utxo={self.utxo}, amount={self.amount}," \ + f" height={self.height}, " \ + f"pubkey={self.pubkey.hex()})" + - def __init__(self, blob: bytes = b""): - self.blob: bytes = blob +class AvalancheSignedStake: + def __init__(self, stake=None, sig=b""): + self.stake: AvalancheStake = stake or AvalancheStake() + self.sig: bytes = sig + """Signature for this stake, bytes of length 64""" + + def deserialize(self, f): + self.stake = AvalancheStake() + self.stake.deserialize(f) + self.sig = f.read(64) + + def serialize(self) -> bytes: + return self.stake.serialize() + self.sig + + def __repr__(self): + return f"AvalancheSignedStake(stake={self.stake}, sig={self.sig.hex()})" + + +class AvalancheProof: + __slots__ = ("sequence", "expiration", "master", "stakes", "proofid") + + def __init__(self, sequence=0, expiration=0, + master=b"", signed_stakes=None): + self.sequence: int = sequence + self.expiration: int = expiration + self.master: bytes = master + + self.stakes: List[AvalancheSignedStake] = signed_stakes or [ + AvalancheSignedStake()] + self.proofid: int = self.compute_proof_id() + + def compute_proof_id(self) -> int: + """Return Bitcoin's 256-bit hash (double SHA-256) of the + serialized proof data. + :return: bytes of length 32 + """ + ss = struct.pack(" 0, + timeout=timeout, + lock=mininode_lock) + + with mininode_lock: + return self.avaresponses.pop(0) + + def send_poll(self, hashes): + msg = msg_avapoll() + msg.poll.round = self.round + self.round += 1 + for h in hashes: + msg.poll.invs.append(CInv(2, h)) + self.send_message(msg) + + def get_avapoll_if_available(self): + with mininode_lock: + return self.avapolls.pop(0) if len(self.avapolls) > 0 else None + + def wait_for_avahello(self, timeout=5): + wait_until( + lambda: self.avahello is not None, + timeout=timeout, + lock=mininode_lock) + + with mininode_lock: + return self.avahello + + def send_avahello(self): + msg = msg_avahello() + msg.hello.delegation.proofid = self.DUMMY_PROOFID + self.send_message(msg) + + def send_getdata(self, inv: List[CInv]): + msg = msg_getdata() + msg.inv = inv + self.send_message(msg) + + def on_avaproof(self, message): + with mininode_lock: + assert(self.avaproof is None) + self.avaproof = message + + def wait_for_avaproof(self, timeout=10): + wait_until( + lambda: self.avaproof is not None, + timeout=timeout, + lock=mininode_lock) + + with mininode_lock: + return self.avaproof