Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche_quorum.py
Show All 18 Lines | |||||
) | ) | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import assert_equal | from test_framework.util import assert_equal | ||||
class AvalancheQuorumTest(BitcoinTestFramework): | class AvalancheQuorumTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 1 | self.num_nodes = 2 | ||||
self.min_avaproofs_node_count = 8 | self.min_avaproofs_node_count = 8 | ||||
self.extra_args = [ | self.extra_args = [[ | ||||
[ | |||||
'-enableavalanche=1', | '-enableavalanche=1', | ||||
'-avacooldown=0', | '-avacooldown=0', | ||||
'-avatimeout=0', | '-avatimeout=0', | ||||
'-avaminquorumstake=100000000', | '-avaminquorumstake=100000000', | ||||
'-avaminquorumconnectedstakeratio=0.8', | '-avaminquorumconnectedstakeratio=0.8', | ||||
f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}', | ]] * self.num_nodes | ||||
] | self.extra_args[0] = self.extra_args[0] + \ | ||||
] | ['-avaminavaproofsnodecount=0'] | ||||
self.extra_args[1] = self.extra_args[1] + \ | |||||
[f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}'] | |||||
def run_test(self): | def run_test(self): | ||||
# Create a local node to poll from and a helper to send polls from it | # Prepare peers proofs | ||||
# and assert on the response | peers = [] | ||||
node = self.nodes[0] | for i in range(0, self.min_avaproofs_node_count): | ||||
poll_node = get_ava_p2p_interface(node) | key, proof = gen_proof(self.nodes[0]) | ||||
poll_node_pubkey = ECPubKey() | peers.append({'key': key, 'proof': proof}) | ||||
poll_node_pubkey.set(bytes.fromhex(node.getavalanchekey())) | |||||
# Let the nodes known about all the blocks then disconnect them so we're | |||||
# sure they won't exchange proofs when we start connecting peers. | |||||
self.sync_all() | |||||
self.disconnect_nodes(0, 1) | |||||
# Build polling nodes | |||||
pollers = [get_ava_p2p_interface(node) for node in self.nodes] | |||||
def poll_and_assert_response(node, expected): | |||||
pubkey = ECPubKey() | |||||
pubkey.set(bytes.fromhex(node.getavalanchekey())) | |||||
poller = pollers[node.index] | |||||
def poll_and_assert_response(expected): | |||||
# Send poll for best block | # Send poll for best block | ||||
block = int(node.getbestblockhash(), 16) | block = int(node.getbestblockhash(), 16) | ||||
poll_node.send_poll([block]) | poller.send_poll([block]) | ||||
# Get response and check that the vote is what we expect | # Get response and check that the vote is what we expect | ||||
response = poll_node.wait_for_avaresponse() | response = poller.wait_for_avaresponse() | ||||
r = response.response | r = response.response | ||||
assert poll_node_pubkey.verify_schnorr(response.sig, r.get_hash()) | assert pubkey.verify_schnorr(response.sig, r.get_hash()) | ||||
assert_equal(len(r.votes), 1) | assert_equal(len(r.votes), 1) | ||||
actual = repr(r.votes[0]) | actual = repr(r.votes[0]) | ||||
expected = repr(AvalancheVote(expected, block)) | expected = repr(AvalancheVote(expected, block)) | ||||
assert_equal(actual, expected) | assert_equal(actual, expected) | ||||
# Prepare peers proofs | def addavalanchenode(node, peer): | ||||
peers = [] | |||||
for i in range(0, self.min_avaproofs_node_count): | |||||
key, proof = gen_proof(node) | |||||
peers.append({'key': key, 'proof': proof}) | |||||
def addavalanchenode(peer): | |||||
pubkey = peer['key'].get_pubkey().get_bytes().hex() | pubkey = peer['key'].get_pubkey().get_bytes().hex() | ||||
assert node.addavalanchenode( | assert node.addavalanchenode( | ||||
peer['node'].nodeid, | peer['node'].nodeid, | ||||
pubkey, | pubkey, | ||||
peer['proof'].serialize().hex(), | peer['proof'].serialize().hex(), | ||||
) is True | ) is True | ||||
p2p_idx = 0 | p2p_idx = 0 | ||||
def get_ava_outbound(n, peer): | def get_ava_outbound(node, peer): | ||||
nonlocal p2p_idx | nonlocal p2p_idx | ||||
avapeer = AvaP2PInterface() | avapeer = AvaP2PInterface() | ||||
n.add_outbound_p2p_connection( | node.add_outbound_p2p_connection( | ||||
avapeer, | avapeer, | ||||
p2p_idx=p2p_idx, | p2p_idx=p2p_idx, | ||||
connection_type="avalanche", | connection_type="avalanche", | ||||
services=NODE_NETWORK | NODE_AVALANCHE, | services=NODE_NETWORK | NODE_AVALANCHE, | ||||
) | ) | ||||
p2p_idx += 1 | p2p_idx += 1 | ||||
avapeer.nodeid = node.getpeerinfo()[-1]['id'] | avapeer.nodeid = node.getpeerinfo()[-1]['id'] | ||||
peer['node'] = avapeer | peer['node'] = avapeer | ||||
addavalanchenode(peer) | addavalanchenode(node, peer) | ||||
avapeer.wait_until( | avapeer.wait_until( | ||||
lambda: avapeer.last_message.get("getavaproofs")) | lambda: avapeer.last_message.get("getavaproofs")) | ||||
avapeer.send_and_ping(msg_avaproofs()) | avapeer.send_and_ping(msg_avaproofs()) | ||||
avapeer.wait_until( | avapeer.wait_until( | ||||
lambda: avapeer.last_message.get("avaproofsreq")) | lambda: avapeer.last_message.get("avaproofsreq")) | ||||
return avapeer | return avapeer | ||||
def add_avapeer_and_check_status(peer, expected_status): | |||||
for i, node in enumerate(self.nodes): | |||||
get_ava_outbound(node, peer) | |||||
poll_and_assert_response(node, expected_status[i]) | |||||
# Start polling. The response should be UNKNOWN because there's no | # Start polling. The response should be UNKNOWN because there's no | ||||
# score | # score | ||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | [poll_and_assert_response(node, AvalancheVoteError.UNKNOWN) | ||||
for node in self.nodes] | |||||
# Create one peer with half the score and add one node | # Create one peer with half the score and add one node | ||||
get_ava_outbound(node, peers[0]) | add_avapeer_and_check_status( | ||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | peers[0], [ | ||||
AvalancheVoteError.UNKNOWN, | |||||
AvalancheVoteError.UNKNOWN, | |||||
]) | |||||
# Create a second peer with the other half and add one node. | # Create a second peer with the other half and add one node. | ||||
# This is not enough because we are lacking avaproofs messages | # This is enough for node0 but not node1 | ||||
get_ava_outbound(node, peers[1]) | add_avapeer_and_check_status( | ||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | peers[1], [ | ||||
AvalancheVoteError.ACCEPTED, | |||||
AvalancheVoteError.UNKNOWN, | |||||
]) | |||||
# Add more peers for triggering the avaproofs messaging | # Add more peers for triggering the avaproofs messaging | ||||
for i in range(2, self.min_avaproofs_node_count - 1): | for i in range(2, self.min_avaproofs_node_count - 1): | ||||
get_ava_outbound(node, peers[i]) | add_avapeer_and_check_status( | ||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | peers[i], [ | ||||
AvalancheVoteError.ACCEPTED, | |||||
get_ava_outbound(node, peers[self.min_avaproofs_node_count - 1]) | AvalancheVoteError.UNKNOWN, | ||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | ]) | ||||
# Disconnect peer 1's node which drops us below the threshold, but we've | add_avapeer_and_check_status( | ||||
# latched that the quorum is established | peers[self.min_avaproofs_node_count - 1], [ | ||||
peers[1]['node'].peer_disconnect() | AvalancheVoteError.ACCEPTED, | ||||
peers[1]['node'].wait_for_disconnect() | AvalancheVoteError.ACCEPTED, | ||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | ]) | ||||
# Reconnect node and re-establish quorum | |||||
get_ava_outbound(node, peers[1]) | |||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvalancheQuorumTest().main() | AvalancheQuorumTest().main() |