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 time import time | from time import time | ||||
from test_framework.avatools import ( | from test_framework.avatools import ( | ||||
AvaP2PInterface, | |||||
create_coinbase_stakes, | create_coinbase_stakes, | ||||
get_ava_p2p_interface, | get_ava_p2p_interface, | ||||
) | ) | ||||
from test_framework.key import ECKey, ECPubKey | from test_framework.key import ECKey, ECPubKey | ||||
from test_framework.messages import AvalancheVote, AvalancheVoteError | from test_framework.messages import ( | ||||
NODE_AVALANCHE, | |||||
NODE_NETWORK, | |||||
AvalancheVote, | |||||
AvalancheVoteError, | |||||
msg_avaproofs, | |||||
) | |||||
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 | ||||
from test_framework.wallet_util import bytes_to_wif | from test_framework.wallet_util import bytes_to_wif | ||||
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 = 1 | ||||
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}', | |||||
] | |||||
] | ] | ||||
def mock_forward(self, delta): | def mock_forward(self, delta): | ||||
self.mock_time += delta | self.mock_time += delta | ||||
self.nodes[0].setmocktime(self.mock_time) | self.nodes[0].setmocktime(self.mock_time) | ||||
def run_test(self): | def run_test(self): | ||||
self.mock_time = int(time()) | self.mock_time = int(time()) | ||||
Show All 17 Lines | def run_test(self): | ||||
assert poll_node_pubkey.verify_schnorr(response.sig, r.get_hash()) | assert poll_node_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) | ||||
# Create peers to poll | # Create peers to poll | ||||
num_quorum_peers = 2 | |||||
coinbase_key = node.get_deterministic_priv_key().key | coinbase_key = node.get_deterministic_priv_key().key | ||||
blocks = node.generate(num_quorum_peers) | blocks = node.generate(self.min_avaproofs_node_count) | ||||
peers = [] | peers = [] | ||||
for i in range(0, num_quorum_peers): | |||||
keyHex = "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f75" + \ | for i in range(0, self.min_avaproofs_node_count): | ||||
str(i) | keyHex = f"12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f7{i:02}" | ||||
k = ECKey() | k = ECKey() | ||||
k.set(bytes.fromhex(keyHex), True) | k.set(bytes.fromhex(keyHex), True) | ||||
stakes = create_coinbase_stakes( | stakes = create_coinbase_stakes( | ||||
node, [blocks[i]], coinbase_key) | node, [blocks[i]], coinbase_key) | ||||
proof = node.buildavalancheproof(1, 1, bytes_to_wif(k.get_bytes()), | proof = node.buildavalancheproof(1, 1, bytes_to_wif(k.get_bytes()), | ||||
stakes) | stakes) | ||||
peers.append({'key': k, 'proof': proof, 'stake': stakes}) | peers.append({'key': k, 'proof': proof, 'stake': stakes}) | ||||
def addavalanchenode(peer): | 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, pubkey, peer['proof']) is True | peer['node'].nodeid, pubkey, peer['proof']) is True | ||||
p2p_idx = 0 | |||||
def get_ava_outbound(n, peer): | |||||
nonlocal p2p_idx | |||||
avapeer = AvaP2PInterface() | |||||
n.add_outbound_p2p_connection( | |||||
avapeer, | |||||
p2p_idx=p2p_idx, | |||||
connection_type="avalanche", | |||||
services=NODE_NETWORK | NODE_AVALANCHE, | |||||
) | |||||
p2p_idx += 1 | |||||
avapeer.nodeid = node.getpeerinfo()[-1]['id'] | |||||
peer['node'] = avapeer | |||||
addavalanchenode(peer) | |||||
avapeer.wait_until( | |||||
lambda: avapeer.last_message.get("getavaproofs")) | |||||
avapeer.send_and_ping(msg_avaproofs()) | |||||
avapeer.wait_until( | |||||
lambda: avapeer.last_message.get("avaproofsreq")) | |||||
return avapeer | |||||
# 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(AvalancheVoteError.UNKNOWN) | ||||
# Create one peer with half the score and add one node | # Create one peer with half the score and add one node | ||||
peers[0]['node'] = get_ava_p2p_interface(node) | get_ava_outbound(node, peers[0]) | ||||
addavalanchenode(peers[0]) | poll_and_assert_response(AvalancheVoteError.UNKNOWN) | ||||
# Create a second peer with the other half and add one node. | |||||
# This is not enough because we are lacking avaproofs messages | |||||
get_ava_outbound(node, peers[1]) | |||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | |||||
# Add more peers for triggering the avaproofs messaging | |||||
for i in range(2, self.min_avaproofs_node_count - 1): | |||||
get_ava_outbound(node, peers[i]) | |||||
poll_and_assert_response(AvalancheVoteError.UNKNOWN) | poll_and_assert_response(AvalancheVoteError.UNKNOWN) | ||||
# Create a second peer with the other half and add one node | get_ava_outbound(node, peers[self.min_avaproofs_node_count - 1]) | ||||
peers[1]['node'] = get_ava_p2p_interface(node) | |||||
addavalanchenode(peers[1]) | |||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | poll_and_assert_response(AvalancheVoteError.ACCEPTED) | ||||
# Disconnect peer 1's node which drops us below the threshold, but we've | # Disconnect peer 1's node which drops us below the threshold, but we've | ||||
# latched that the quorum is established | # latched that the quorum is established | ||||
self.mock_forward(1) | self.mock_forward(1) | ||||
peers[1]['node'].peer_disconnect() | peers[1]['node'].peer_disconnect() | ||||
peers[1]['node'].wait_for_disconnect() | peers[1]['node'].wait_for_disconnect() | ||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | poll_and_assert_response(AvalancheVoteError.ACCEPTED) | ||||
# Reconnect node and re-establish quorum | # Reconnect node and re-establish quorum | ||||
peers[1]['node'] = get_ava_p2p_interface(node) | get_ava_outbound(node, peers[1]) | ||||
addavalanchenode(peers[1]) | |||||
poll_and_assert_response(AvalancheVoteError.ACCEPTED) | poll_and_assert_response(AvalancheVoteError.ACCEPTED) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvalancheQuorumTest().main() | AvalancheQuorumTest().main() |