Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche_quorum.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2020-2022 The Bitcoin developers | # Copyright (c) 2020-2022 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 the quorum detection of avalanche.""" | """Test the quorum detection of avalanche.""" | ||||
from test_framework.avatools import ( | from test_framework.avatools import ( | ||||
AvaP2PInterface, | AvaP2PInterface, | ||||
build_msg_avaproofs, | build_msg_avaproofs, | ||||
gen_proof, | gen_proof, | ||||
get_ava_p2p_interface, | get_ava_p2p_interface, | ||||
wait_for_proof, | |||||
) | ) | ||||
from test_framework.key import ECPubKey | from test_framework.key import ECPubKey | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
NODE_AVALANCHE, | NODE_AVALANCHE, | ||||
NODE_NETWORK, | NODE_NETWORK, | ||||
AvalancheVote, | AvalancheVote, | ||||
AvalancheVoteError, | AvalancheVoteError, | ||||
) | ) | ||||
from test_framework.p2p import p2p_lock | from test_framework.p2p import p2p_lock | ||||
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 = 3 | self.num_nodes = 3 | ||||
self.min_avaproofs_node_count = 8 | self.min_avaproofs_node_count = 8 | ||||
self.extra_args = [[ | self.extra_args = [[ | ||||
'-enableavalanche=1', | '-enableavalanche=1', | ||||
'-enableavalanchepeerdiscovery=1', | '-enableavalanchepeerdiscovery=1', | ||||
'-avaproofstakeutxoconfirmations=1', | '-avaproofstakeutxoconfirmations=1', | ||||
'-avacooldown=0', | '-avacooldown=0', | ||||
'-avatimeout=0', | '-avatimeout=0', | ||||
'-avaminquorumstake=100000000', | '-avaminquorumstake=150000000', | ||||
'-avaminquorumconnectedstakeratio=0.8', | '-avaminquorumconnectedstakeratio=0.8', | ||||
'-minimumchainwork=0', | '-minimumchainwork=0', | ||||
]] * self.num_nodes | ]] * self.num_nodes | ||||
self.extra_args[0] = self.extra_args[0] + \ | self.extra_args[0] = self.extra_args[0] + \ | ||||
['-avaminavaproofsnodecount=0'] | ['-avaminavaproofsnodecount=0'] | ||||
self.extra_args[1] = self.extra_args[1] + \ | self.extra_args[1] = self.extra_args[1] + \ | ||||
[f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}'] | [f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}'] | ||||
self.extra_args[2] = self.extra_args[2] + \ | self.extra_args[2] = self.extra_args[2] + \ | ||||
[f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}'] | [f'-avaminavaproofsnodecount={self.min_avaproofs_node_count}'] | ||||
def run_test(self): | def run_test(self): | ||||
# Initially all nodes start with 8 nodes attached to a single proof | |||||
privkey, proof = gen_proof(self.nodes[0]) | |||||
for node in self.nodes: | |||||
quorum = [get_ava_p2p_interface(node) | |||||
for _ in range(0, 8)] | |||||
for n in quorum: | |||||
success = node.addavalanchenode( | |||||
n.nodeid, | |||||
privkey.get_pubkey().get_bytes().hex(), | |||||
proof.serialize().hex(), | |||||
) | |||||
assert success is True | |||||
# Prepare peers proofs | # Prepare peers proofs | ||||
peers = [] | peers = [] | ||||
for i in range(0, self.min_avaproofs_node_count + 1): | for i in range(0, self.min_avaproofs_node_count + 1): | ||||
key, proof = gen_proof(self.nodes[0]) | key, proof = gen_proof(self.nodes[0]) | ||||
peers.append({'key': key, 'proof': proof}) | peers.append({'key': key, 'proof': proof}) | ||||
# Let the nodes known about all the blocks then disconnect them so we're | # 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. | # sure they won't exchange proofs when we start connecting peers. | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
return avapeer | return avapeer | ||||
def add_avapeer_and_check_status( | def add_avapeer_and_check_status( | ||||
peer, expected_status, empty_avaproof=False): | peer, expected_status, empty_avaproof=False): | ||||
for i, node in enumerate(self.nodes): | for i, node in enumerate(self.nodes): | ||||
get_ava_outbound(node, peer, empty_avaproof) | get_ava_outbound(node, peer, empty_avaproof) | ||||
poll_and_assert_response(node, expected_status[i]) | 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 not | ||||
# score | # enough stake | ||||
[poll_and_assert_response(node, AvalancheVoteError.UNKNOWN) | [poll_and_assert_response(node, AvalancheVoteError.UNKNOWN) | ||||
for node in self.nodes] | for node in self.nodes] | ||||
# Create one peer with half the score and add one node | # Create one peer with half the remaining missing stake and add one | ||||
# node | |||||
add_avapeer_and_check_status( | add_avapeer_and_check_status( | ||||
peers[0], [ | peers[0], [ | ||||
AvalancheVoteError.UNKNOWN, | AvalancheVoteError.UNKNOWN, | ||||
AvalancheVoteError.UNKNOWN, | AvalancheVoteError.UNKNOWN, | ||||
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. | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# The messages are accounted and the node quorum finally valid | # The messages are accounted and the node quorum finally valid | ||||
add_avapeer_and_check_status( | add_avapeer_and_check_status( | ||||
peers[self.min_avaproofs_node_count], [ | peers[self.min_avaproofs_node_count], [ | ||||
AvalancheVoteError.ACCEPTED, | AvalancheVoteError.ACCEPTED, | ||||
AvalancheVoteError.ACCEPTED, | AvalancheVoteError.ACCEPTED, | ||||
AvalancheVoteError.ACCEPTED, | AvalancheVoteError.ACCEPTED, | ||||
]) | ]) | ||||
# Unless there is not enough nodes to poll | |||||
for node in self.nodes: | |||||
avapeers = [p2p for p2p in node.p2ps if p2p not in pollers] | |||||
for peer in avapeers[7:]: | |||||
peer.peer_disconnect() | |||||
peer.wait_for_disconnect() | |||||
poll_and_assert_response(node, AvalancheVoteError.UNKNOWN) | |||||
# Add a node back and check it resumes the quorum status | |||||
avapeer = AvaP2PInterface(node) | |||||
node.add_p2p_connection(avapeer) | |||||
wait_for_proof(node, f"{avapeer.proof.proofid:0{64}x}") | |||||
poll_and_assert_response(node, AvalancheVoteError.ACCEPTED) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvalancheQuorumTest().main() | AvalancheQuorumTest().main() |