Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_proof_inventory.py
Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | def on_inv(self, message): | ||||
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 | ||||
self.last_proofid = i.hash | self.last_proofid = i.hash | ||||
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 = [ | ||||
'-avaproofstakeutxodustthreshold=1000000', | [ | ||||
'-avaproofstakeutxoconfirmations=2', | "-avaproofstakeutxodustthreshold=1000000", | ||||
'-avacooldown=0', | "-avaproofstakeutxoconfirmations=2", | ||||
'-whitelist=noban@127.0.0.1', | "-avacooldown=0", | ||||
]] * self.num_nodes | "-whitelist=noban@127.0.0.1", | ||||
] | |||||
] * self.num_nodes | |||||
def generate_proof(self, node, mature=True): | def generate_proof(self, node, mature=True): | ||||
privkey, proof = gen_proof(self, node) | privkey, proof = gen_proof(self, node) | ||||
if mature: | if mature: | ||||
self.generate(node, 1, sync_fun=self.no_op) | self.generate(node, 1, sync_fun=self.no_op) | ||||
return privkey, proof | return privkey, proof | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | class ProofInventoryTest(BitcoinTestFramework): | ||||
def test_ban_invalid_proof(self): | def test_ban_invalid_proof(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
_, bad_proof = self.generate_proof(node) | _, bad_proof = self.generate_proof(node) | ||||
bad_proof.stakes = [] | bad_proof.stakes = [] | ||||
privkey = node.get_deterministic_priv_key().key | privkey = node.get_deterministic_priv_key().key | ||||
missing_stake = node.buildavalancheproof( | missing_stake = node.buildavalancheproof( | ||||
1, 0, privkey, [{ | 1, | ||||
'txid': '0' * 64, | 0, | ||||
'vout': 0, | privkey, | ||||
'amount': 10000000, | [ | ||||
'height': 42, | { | ||||
'iscoinbase': False, | "txid": "0" * 64, | ||||
'privatekey': privkey, | "vout": 0, | ||||
}] | "amount": 10000000, | ||||
"height": 42, | |||||
"iscoinbase": False, | |||||
"privatekey": privkey, | |||||
} | |||||
], | |||||
) | ) | ||||
self.restart_node( | self.restart_node(0, ["-avaproofstakeutxodustthreshold=1000000"]) | ||||
0, ['-avaproofstakeutxodustthreshold=1000000']) | |||||
peer = node.add_p2p_connection(P2PInterface()) | peer = node.add_p2p_connection(P2PInterface()) | ||||
msg = msg_avaproof() | msg = msg_avaproof() | ||||
# Sending a proof with a missing utxo doesn't trigger a ban | # Sending a proof with a missing utxo doesn't trigger a ban | ||||
msg.proof = avalanche_proof_from_hex(missing_stake) | msg.proof = avalanche_proof_from_hex(missing_stake) | ||||
with node.assert_debug_log(["received: avaproof"], ["Misbehaving"]): | with node.assert_debug_log(["received: avaproof"], ["Misbehaving"]): | ||||
peer.send_message(msg) | peer.send_message(msg) | ||||
peer.sync_with_ping() | peer.sync_with_ping() | ||||
msg.proof = bad_proof | msg.proof = bad_proof | ||||
with node.assert_debug_log([ | with node.assert_debug_log( | ||||
'Misbehaving', | [ | ||||
'invalid-proof', | "Misbehaving", | ||||
]): | "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 less than 2 nodes ! | # This test makes no sense with less than 2 nodes ! | ||||
assert_greater_than(self.num_nodes, 2) | assert_greater_than(self.num_nodes, 2) | ||||
proofs_keys = [self.generate_proof(self.nodes[0]) for _ in self.nodes] | proofs_keys = [self.generate_proof(self.nodes[0]) for _ in self.nodes] | ||||
proofids = {proof_key[1].proofid for proof_key in proofs_keys} | proofids = {proof_key[1].proofid for proof_key in proofs_keys} | ||||
# generate_proof does not sync, so do it manually | # generate_proof does not sync, so do it manually | ||||
self.sync_blocks() | self.sync_blocks() | ||||
def restart_nodes_with_proof(nodes, extra_args=None): | def restart_nodes_with_proof(nodes, extra_args=None): | ||||
for node in nodes: | for node in nodes: | ||||
privkey, proof = proofs_keys[node.index] | privkey, proof = proofs_keys[node.index] | ||||
self.restart_node(node.index, self.extra_args[node.index] + [ | self.restart_node( | ||||
node.index, | |||||
self.extra_args[node.index] | |||||
+ [ | |||||
f"-avaproof={proof.serialize().hex()}", | f"-avaproof={proof.serialize().hex()}", | ||||
f"-avamasterkey={bytes_to_wif(privkey.get_bytes())}" | f"-avamasterkey={bytes_to_wif(privkey.get_bytes())}", | ||||
] + (extra_args or [])) | ] | ||||
+ (extra_args or []), | |||||
) | |||||
restart_nodes_with_proof(self.nodes[:-1]) | restart_nodes_with_proof(self.nodes[:-1]) | ||||
chainwork = int(self.nodes[-1].getblockchaininfo()['chainwork'], 16) | chainwork = int(self.nodes[-1].getblockchaininfo()["chainwork"], 16) | ||||
restart_nodes_with_proof( | restart_nodes_with_proof( | ||||
self.nodes[-1:], extra_args=[f'-minimumchainwork={chainwork + 100:#x}']) | self.nodes[-1:], extra_args=[f"-minimumchainwork={chainwork + 100:#x}"] | ||||
) | |||||
# Add an inbound so the node proof can be registered and advertised | # Add an inbound so the node proof can be registered and advertised | ||||
[node.add_p2p_connection(P2PInterface()) for node in self.nodes] | [node.add_p2p_connection(P2PInterface()) for node in self.nodes] | ||||
[[self.connect_nodes(node.index, j) | [ | ||||
for j in range(node.index)] for node in self.nodes] | [self.connect_nodes(node.index, j) for j in range(node.index)] | ||||
for node in self.nodes | |||||
] | |||||
# Connect a block to make the proofs added to our pool | # Connect a block to make the proofs added to our pool | ||||
self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks) | self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks) | ||||
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.nodes[:-1]) | self.sync_proofs(self.nodes[:-1]) | ||||
for node in self.nodes[:-1]: | for node in self.nodes[:-1]: | ||||
assert_equal(set(get_proof_ids(node)), proofids) | assert_equal(set(get_proof_ids(node)), proofids) | ||||
assert self.nodes[-1].getblockchaininfo()['initialblockdownload'] | assert self.nodes[-1].getblockchaininfo()["initialblockdownload"] | ||||
self.log.info("Except the node that has not completed IBD") | self.log.info("Except the node that has not completed IBD") | ||||
assert_equal(len(get_proof_ids(self.nodes[-1])), 1) | assert_equal(len(get_proof_ids(self.nodes[-1])), 1) | ||||
# The same if we send a proof directly with no download request | # The same if we send a proof directly with no download request | ||||
peer = AvaP2PInterface() | peer = AvaP2PInterface() | ||||
self.nodes[-1].add_p2p_connection(peer) | self.nodes[-1].add_p2p_connection(peer) | ||||
_, proof = self.generate_proof(self.nodes[0]) | _, proof = self.generate_proof(self.nodes[0]) | ||||
peer.send_avaproof(proof) | peer.send_avaproof(proof) | ||||
peer.sync_send_with_ping() | peer.sync_send_with_ping() | ||||
with p2p_lock: | with p2p_lock: | ||||
assert_equal(peer.message_count.get('getdata', 0), 0) | assert_equal(peer.message_count.get("getdata", 0), 0) | ||||
# Leave the nodes in good shape for the next tests | # Leave the nodes in good shape for the next tests | ||||
restart_nodes_with_proof(self.nodes) | restart_nodes_with_proof(self.nodes) | ||||
[[self.connect_nodes(node.index, j) | [ | ||||
for j in range(node.index)] for node in self.nodes] | [self.connect_nodes(node.index, j) for j in range(node.index)] | ||||
for node in self.nodes | |||||
] | |||||
def test_manually_sent_proof(self): | def test_manually_sent_proof(self): | ||||
node0 = self.nodes[0] | node0 = self.nodes[0] | ||||
_, proof = self.generate_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] | ||||
Show All 15 Lines | def test_unbroadcast(self): | ||||
# 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): | ||||
with p2p_lock: | with p2p_lock: | ||||
return all(p.last_message.get( | return all( | ||||
"inv") and p.last_message["inv"].inv[-1].hash == proof.proofid for p in peers) | 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)) | self.wait_until(lambda: proof_inv_received(peers)) | ||||
# If no peer request the proof for download, the node should reattempt | # If no peer request the proof for download, the node should reattempt | ||||
# broadcasting to all new peers after 10 to 15 minutes. | # broadcasting to all new peers after 10 to 15 minutes. | ||||
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() | ||||
self.wait_until(lambda: proof_inv_received(peers)) | self.wait_until(lambda: proof_inv_received(peers)) | ||||
# If at least one peer requests the proof, there is no more attempt to | # If at least one peer requests the proof, there is no more attempt to | ||||
# broadcast it | # broadcast it | ||||
node.setmocktime(int(time.time()) + UNCONDITIONAL_RELAY_DELAY) | node.setmocktime(int(time.time()) + UNCONDITIONAL_RELAY_DELAY) | ||||
msg = msg_getdata([CInv(t=MSG_AVA_PROOF, h=proof.proofid)]) | msg = msg_getdata([CInv(t=MSG_AVA_PROOF, h=proof.proofid)]) | ||||
peers[-1].send_message(msg) | peers[-1].send_message(msg) | ||||
# Give enough time for the node to broadcast the proof again | # Give enough time for the node to broadcast the proof again | ||||
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) | ||||
self.log.info( | self.log.info("Proofs that become invalid should no longer be broadcasted") | ||||
"Proofs that become invalid should no longer be broadcasted") | |||||
# Restart and add connect a new set of peers | # Restart and add connect a new set of peers | ||||
self.restart_node(0) | self.restart_node(0) | ||||
# 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()) | ||||
self.wait_until(lambda: proof_inv_received(peers)) | self.wait_until(lambda: proof_inv_received(peers)) | ||||
# Sanity check our node knows the proof, and it is valid | # Sanity check our node knows the proof, and it is valid | ||||
wait_for_proof(node, proofid_hex) | wait_for_proof(node, proofid_hex) | ||||
# Mature the utxo then spend it | # Mature the utxo then spend it | ||||
self.generate(node, 100, sync_fun=self.no_op) | self.generate(node, 100, sync_fun=self.no_op) | ||||
utxo = proof.stakes[0].stake.utxo | utxo = proof.stakes[0].stake.utxo | ||||
raw_tx = node.createrawtransaction( | raw_tx = node.createrawtransaction( | ||||
inputs=[{ | inputs=[ | ||||
{ | |||||
# coinbase | # coinbase | ||||
"txid": uint256_hex(utxo.txid), | "txid": uint256_hex(utxo.txid), | ||||
"vout": utxo.n | "vout": utxo.n, | ||||
}], | } | ||||
], | |||||
outputs={ADDRESS_ECREG_UNSPENDABLE: 25_000_000 - 250.00}, | outputs={ADDRESS_ECREG_UNSPENDABLE: 25_000_000 - 250.00}, | ||||
) | ) | ||||
signed_tx = node.signrawtransactionwithkey( | signed_tx = node.signrawtransactionwithkey( | ||||
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 | ||||
self.generate(node, 1, sync_fun=self.no_op) | self.generate(node, 1, sync_fun=self.no_op) | ||||
# Wait for the proof to be invalidated | # Wait for the proof to be invalidated | ||||
def check_proof_not_found(proofid): | def check_proof_not_found(proofid): | ||||
try: | try: | ||||
assert_raises_rpc_error(-8, | assert_raises_rpc_error( | ||||
"Proof not found", | -8, "Proof not found", node.getrawavalancheproof, proofid | ||||
node.getrawavalancheproof, | ) | ||||
proofid) | |||||
return True | return True | ||||
except BaseException: | except BaseException: | ||||
return False | return False | ||||
self.wait_until(lambda: check_proof_not_found(proofid_hex)) | 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) | ||||
def run_test(self): | def run_test(self): | ||||
self.test_send_proof_inv() | self.test_send_proof_inv() | ||||
self.test_receive_proof() | self.test_receive_proof() | ||||
self.test_proof_relay() | self.test_proof_relay() | ||||
self.test_manually_sent_proof() | self.test_manually_sent_proof() | ||||
# Run these tests last because they need to disconnect the nodes | # Run these tests last because they need to disconnect the nodes | ||||
self.test_unbroadcast() | self.test_unbroadcast() | ||||
self.test_ban_invalid_proof() | self.test_ban_invalid_proof() | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
ProofInventoryTest().main() | ProofInventoryTest().main() |