Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_proof_inventory.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2021 The Bitcoin developers | # Copyright (c) 2021 The Bitcoin 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 proof inventory relaying | Test proof inventory relaying | ||||
""" | """ | ||||
import time | import time | ||||
from test_framework.address import ADDRESS_ECREG_UNSPENDABLE | from test_framework.address import ADDRESS_ECREG_UNSPENDABLE | ||||
from test_framework.avatools import ( | from test_framework.avatools import gen_proof, get_proof_ids, wait_for_proof | ||||
avalanche_proof_from_hex, | |||||
gen_proof, | |||||
get_proof_ids, | |||||
wait_for_proof, | |||||
) | |||||
from test_framework.key import ECKey | |||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
MSG_AVA_PROOF, | MSG_AVA_PROOF, | ||||
MSG_TYPE_MASK, | MSG_TYPE_MASK, | ||||
CInv, | CInv, | ||||
msg_avaproof, | msg_avaproof, | ||||
msg_getdata, | msg_getdata, | ||||
) | ) | ||||
from test_framework.p2p import P2PInterface, p2p_lock | 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, | |||||
assert_raises_rpc_error, | |||||
) | |||||
from test_framework.wallet_util import bytes_to_wif | from test_framework.wallet_util import bytes_to_wif | ||||
# Broadcast reattempt occurs every 10 to 15 minutes | # Broadcast reattempt occurs every 10 to 15 minutes | ||||
MAX_INITIAL_BROADCAST_DELAY = 15 * 60 | MAX_INITIAL_BROADCAST_DELAY = 15 * 60 | ||||
# Delay to allow the node to respond to getdata requests | # Delay to allow the node to respond to getdata requests | ||||
UNCONDITIONAL_RELAY_DELAY = 2 * 60 | UNCONDITIONAL_RELAY_DELAY = 2 * 60 | ||||
class ProofInvStoreP2PInterface(P2PInterface): | class ProofInvStoreP2PInterface(P2PInterface): | ||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
self.proof_invs_counter = 0 | self.proof_invs_counter = 0 | ||||
def on_inv(self, message): | def on_inv(self, message): | ||||
for i in message.inv: | for i in message.inv: | ||||
if i.type & MSG_TYPE_MASK == MSG_AVA_PROOF: | if i.type & MSG_TYPE_MASK == MSG_AVA_PROOF: | ||||
self.proof_invs_counter += 1 | self.proof_invs_counter += 1 | ||||
class ProofInventoryTest(BitcoinTestFramework): | class ProofInventoryTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 5 | self.num_nodes = 5 | ||||
self.extra_args = [[ | self.extra_args = [[ | ||||
'-enableavalanche=1', | '-enableavalanche=1', | ||||
'-avaproofstakeutxoconfirmations=1', | '-avaproofstakeutxoconfirmations=2', | ||||
'-avacooldown=0', | '-avacooldown=0', | ||||
'-whitelist=noban@127.0.0.1', | '-whitelist=noban@127.0.0.1', | ||||
]] * self.num_nodes | ]] * self.num_nodes | ||||
def generate_proof(self, node, mature=True): | |||||
privkey, proof = gen_proof(node) | |||||
if mature: | |||||
node.generate(1) | |||||
return privkey, proof | |||||
def test_send_proof_inv(self): | def test_send_proof_inv(self): | ||||
self.log.info("Test sending a proof to our peers") | self.log.info("Test sending a proof to our peers") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
for i in range(10): | for i in range(10): | ||||
node.add_p2p_connection(ProofInvStoreP2PInterface()) | node.add_p2p_connection(ProofInvStoreP2PInterface()) | ||||
_, proof = gen_proof(node) | _, proof = self.generate_proof(node) | ||||
assert node.sendavalancheproof(proof.serialize().hex()) | assert node.sendavalancheproof(proof.serialize().hex()) | ||||
def proof_inv_found(peer): | def proof_inv_found(peer): | ||||
with p2p_lock: | with p2p_lock: | ||||
return peer.last_message.get( | return peer.last_message.get( | ||||
"inv") and peer.last_message["inv"].inv[-1].hash == proof.proofid | "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.wait_until(lambda: all(proof_inv_found(i) for i in node.p2ps)) | ||||
Show All 16 Lines | def test_send_proof_inv(self): | ||||
peer.sync_with_ping() | peer.sync_with_ping() | ||||
assert all(p.proof_invs_counter == 1 for p in node.p2ps) | assert all(p.proof_invs_counter == 1 for p in node.p2ps) | ||||
def test_receive_proof(self): | def test_receive_proof(self): | ||||
self.log.info("Test a peer is created on proof reception") | self.log.info("Test a peer is created on proof reception") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
_, proof = gen_proof(node) | _, proof = self.generate_proof(node) | ||||
peer = node.add_p2p_connection(P2PInterface()) | peer = node.add_p2p_connection(P2PInterface()) | ||||
msg = msg_avaproof() | msg = msg_avaproof() | ||||
msg.proof = proof | msg.proof = proof | ||||
peer.send_message(msg) | peer.send_message(msg) | ||||
self.wait_until(lambda: proof.proofid in get_proof_ids(node)) | self.wait_until(lambda: proof.proofid in get_proof_ids(node)) | ||||
self.log.info("Test receiving a proof with missing utxo is orphaned") | self.log.info( | ||||
"Test receiving a proof with an immature 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 = self.generate_proof(node, mature=False) | ||||
orphan_proofid = "{:064x}".format(orphan.proofid) | orphan_proofid = "{:064x}".format(orphan.proofid) | ||||
msg = msg_avaproof() | msg = msg_avaproof() | ||||
msg.proof = orphan | msg.proof = orphan | ||||
peer.send_message(msg) | peer.send_message(msg) | ||||
wait_for_proof(node, orphan_proofid, expect_orphan=True) | wait_for_proof(node, orphan_proofid, expect_orphan=True) | ||||
def test_ban_invalid_proof(self): | def test_ban_invalid_proof(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
_, bad_proof = gen_proof(node) | _, bad_proof = self.generate_proof(node) | ||||
bad_proof.stakes = [] | bad_proof.stakes = [] | ||||
self.restart_node(0, ['-enableavalanche=1']) | self.restart_node(0, ['-enableavalanche=1']) | ||||
peer = node.add_p2p_connection(P2PInterface()) | peer = node.add_p2p_connection(P2PInterface()) | ||||
msg = msg_avaproof() | msg = msg_avaproof() | ||||
msg.proof = bad_proof | msg.proof = bad_proof | ||||
with node.assert_debug_log([ | with node.assert_debug_log([ | ||||
'Misbehaving', | 'Misbehaving', | ||||
'invalid-proof', | 'invalid-proof', | ||||
]): | ]): | ||||
peer.send_message(msg) | peer.send_message(msg) | ||||
peer.wait_for_disconnect() | peer.wait_for_disconnect() | ||||
def test_proof_relay(self): | def test_proof_relay(self): | ||||
# This test makes no sense with a single node ! | # This test makes no sense with a single node ! | ||||
assert_greater_than(self.num_nodes, 1) | assert_greater_than(self.num_nodes, 1) | ||||
node = self.nodes[0] | |||||
def restart_nodes_with_proof(nodes=self.nodes): | def restart_nodes_with_proof(nodes=self.nodes): | ||||
proofids = set() | proofs = [] | ||||
for i, node in enumerate(nodes): | for i in range(self.num_nodes): | ||||
privkey, proof = gen_proof(node) | proofs.append(self.generate_proof(self.nodes[0])) | ||||
proofids.add(proof.proofid) | self.sync_all() | ||||
# Restart nodes with new proofs | |||||
for i, node in enumerate(nodes): | |||||
privkey, proof = proofs[i] | |||||
self.restart_node(node.index, self.extra_args[node.index] + [ | self.restart_node(node.index, self.extra_args[node.index] + [ | ||||
"-avaproof={}".format(proof.serialize().hex()), | "-avaproof={}".format(proof.serialize().hex()), | ||||
"-avamasterkey={}".format(bytes_to_wif(privkey.get_bytes())) | "-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)] | [self.connect_nodes(node.index, j) for j in range(node.index)] | ||||
return proofids | return set(proof.proofid for _, proof in proofs) | ||||
proofids = restart_nodes_with_proof(self.nodes) | proofids = restart_nodes_with_proof(self.nodes) | ||||
# Connect a block to make the proofs added to our pool | |||||
node.generate(1) | |||||
self.sync_all() | |||||
self.log.info("Nodes should eventually get the proof from their peer") | self.log.info("Nodes should eventually get the proof from their peer") | ||||
self.sync_proofs() | self.sync_proofs() | ||||
for node in self.nodes: | for node in self.nodes: | ||||
assert_equal(set(get_proof_ids(node)), proofids) | assert_equal(set(get_proof_ids(node)), proofids) | ||||
def test_manually_sent_proof(self): | def test_manually_sent_proof(self): | ||||
node0 = self.nodes[0] | node0 = self.nodes[0] | ||||
_, proof = gen_proof(node0) | _, proof = self.generate_proof(node0) | ||||
self.log.info( | self.log.info( | ||||
"Send a proof via RPC and check all the nodes download it") | "Send a proof via RPC and check all the nodes download it") | ||||
node0.sendavalancheproof(proof.serialize().hex()) | node0.sendavalancheproof(proof.serialize().hex()) | ||||
self.sync_proofs() | self.sync_proofs() | ||||
def test_unbroadcast(self): | def test_unbroadcast(self): | ||||
self.log.info("Test broadcasting proofs") | self.log.info("Test broadcasting proofs") | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
# Disconnect the other nodes/peers, or they will request the proof and | # Disconnect the other nodes/peers, or they will request the proof and | ||||
# invalidate the test | # invalidate the test | ||||
[n.stop_node() for n in self.nodes[1:]] | [n.stop_node() for n in self.nodes[1:]] | ||||
node.disconnect_p2ps() | node.disconnect_p2ps() | ||||
def add_peers(count): | def add_peers(count): | ||||
peers = [] | peers = [] | ||||
for i in range(count): | for i in range(count): | ||||
peer = node.add_p2p_connection(ProofInvStoreP2PInterface()) | peer = node.add_p2p_connection(ProofInvStoreP2PInterface()) | ||||
peer.wait_for_verack() | peer.wait_for_verack() | ||||
peers.append(peer) | peers.append(peer) | ||||
return peers | return peers | ||||
_, proof = gen_proof(node) | _, proof = self.generate_proof(node) | ||||
proofid_hex = "{:064x}".format(proof.proofid) | proofid_hex = "{:064x}".format(proof.proofid) | ||||
# Broadcast the proof | # Broadcast the proof | ||||
peers = add_peers(3) | peers = add_peers(3) | ||||
assert node.sendavalancheproof(proof.serialize().hex()) | assert node.sendavalancheproof(proof.serialize().hex()) | ||||
wait_for_proof(node, proofid_hex) | wait_for_proof(node, proofid_hex) | ||||
def proof_inv_received(peers): | def proof_inv_received(peers): | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | def test_unbroadcast(self): | ||||
hexstring=raw_tx, | hexstring=raw_tx, | ||||
privkeys=[node.get_deterministic_priv_key().key], | privkeys=[node.get_deterministic_priv_key().key], | ||||
) | ) | ||||
node.sendrawtransaction(signed_tx['hex']) | node.sendrawtransaction(signed_tx['hex']) | ||||
# Mine the tx in a block | # Mine the tx in a block | ||||
node.generate(1) | node.generate(1) | ||||
# Wait for the proof to be orphaned | # Wait for the proof to be invalidated | ||||
self.wait_until(lambda: node.getrawavalancheproof( | def check_proof_not_found(proofid): | ||||
proofid_hex)["orphan"] is True) | try: | ||||
assert_raises_rpc_error(-8, | |||||
"Proof not found", | |||||
node.getrawavalancheproof, | |||||
proofid) | |||||
return True | |||||
except BaseException: | |||||
return False | |||||
self.wait_until(lambda: check_proof_not_found(proofid_hex)) | |||||
# It should no longer be broadcasted | # It should no longer be broadcasted | ||||
peers = add_peers(3) | peers = add_peers(3) | ||||
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY + 1) | node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY + 1) | ||||
peers[-1].sync_with_ping() | peers[-1].sync_with_ping() | ||||
assert not proof_inv_received(peers) | assert not proof_inv_received(peers) | ||||
Show All 13 Lines |