Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche_voting.py
- This file was moved from test/functional/abc_p2p_avalanche.py.
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2020-2021 The Bitcoin developers | # Copyright (c) 2020-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 the resolution of forks via avalanche.""" | """Test the resolution of forks via avalanche.""" | ||||
import random | import random | ||||
import time | |||||
from test_framework.avatools import ( | from test_framework.avatools import ( | ||||
get_ava_p2p_interface, | get_ava_p2p_interface, | ||||
create_coinbase_stakes, | create_coinbase_stakes, | ||||
get_proof_ids, | |||||
) | ) | ||||
from test_framework.key import ( | from test_framework.key import ( | ||||
bytes_to_wif, | |||||
ECKey, | ECKey, | ||||
ECPubKey, | ECPubKey, | ||||
) | ) | ||||
from test_framework.p2p import p2p_lock | from test_framework.messages import AvalancheVote | ||||
from test_framework.messages import ( | |||||
AvalancheProof, | |||||
AvalancheVote, | |||||
CInv, | |||||
FromHex, | |||||
MSG_AVA_PROOF, | |||||
msg_getdata, | |||||
NODE_AVALANCHE, | |||||
NODE_NETWORK, | |||||
) | |||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
wait_until, | wait_until, | ||||
) | ) | ||||
BLOCK_ACCEPTED = 0 | BLOCK_ACCEPTED = 0 | ||||
BLOCK_INVALID = 1 | BLOCK_INVALID = 1 | ||||
BLOCK_PARKED = 2 | BLOCK_PARKED = 2 | ||||
BLOCK_FORK = 3 | BLOCK_FORK = 3 | ||||
BLOCK_UNKNOWN = -1 | BLOCK_UNKNOWN = -1 | ||||
BLOCK_MISSING = -2 | BLOCK_MISSING = -2 | ||||
BLOCK_PENDING = -3 | BLOCK_PENDING = -3 | ||||
QUORUM_NODE_COUNT = 16 | QUORUM_NODE_COUNT = 16 | ||||
UNCONDITIONAL_RELAY_DELAY = 2 * 60 | |||||
class AvalancheTest(BitcoinTestFramework): | class AvalancheTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [ | self.extra_args = [ | ||||
['-enableavalanche=1', '-avacooldown=0'], | ['-enableavalanche=1', '-avacooldown=0'], | ||||
['-enableavalanche=1', '-avacooldown=0', '-noparkdeepreorg', '-maxreorgdepth=-1']] | ['-enableavalanche=1', '-avacooldown=0', '-noparkdeepreorg', '-maxreorgdepth=-1']] | ||||
▲ Show 20 Lines • Show All 202 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
self.log.info( | self.log.info( | ||||
"Check the node is discouraging unexpected avaresponses.") | "Check the node is discouraging unexpected avaresponses.") | ||||
with self.nodes[0].assert_debug_log( | with self.nodes[0].assert_debug_log( | ||||
['Misbehaving', 'peer=1 (0 -> 2): unexpected-ava-response']): | ['Misbehaving', 'peer=1 (0 -> 2): unexpected-ava-response']): | ||||
# unknown voting round | # unknown voting round | ||||
poll_node.send_avaresponse( | poll_node.send_avaresponse( | ||||
round=2**32 - 1, votes=[], privkey=privkey) | round=2**32 - 1, votes=[], privkey=privkey) | ||||
self.log.info( | |||||
"Check the node is signalling the avalanche service bit only if there is a proof.") | |||||
assert_equal( | |||||
int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, | |||||
0) | |||||
# Restart the node | |||||
self.restart_node(0, self.extra_args[0] + [ | |||||
"-avaproof={}".format(proof), | |||||
"-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", | |||||
]) | |||||
assert_equal( | |||||
int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, | |||||
NODE_AVALANCHE) | |||||
self.log.info("Test the avahello signature (node -> P2PInterface)") | |||||
good_interface = get_ava_p2p_interface(node) | |||||
avahello = good_interface.wait_for_avahello().hello | |||||
avakey.set(bytes.fromhex(node.getavalanchekey())) | |||||
assert avakey.verify_schnorr( | |||||
avahello.sig, avahello.get_sighash(good_interface)) | |||||
stakes = create_coinbase_stakes(node, [blockhashes[1]], addrkey0.key) | |||||
interface_proof_hex = node.buildavalancheproof( | |||||
proof_sequence, proof_expiration, pubkey.get_bytes().hex(), | |||||
stakes) | |||||
limited_id = FromHex( | |||||
AvalancheProof(), | |||||
interface_proof_hex).limited_proofid | |||||
# delegate | |||||
delegated_key = ECKey() | |||||
delegated_key.generate() | |||||
interface_delegation_hex = node.delegateavalancheproof( | |||||
f"{limited_id:0{64}x}", | |||||
bytes_to_wif(privkey.get_bytes()), | |||||
delegated_key.get_pubkey().get_bytes().hex(), | |||||
None) | |||||
self.log.info("Test that wrong avahello signature causes a ban") | |||||
bad_interface = get_ava_p2p_interface(node) | |||||
wrong_key = ECKey() | |||||
wrong_key.generate() | |||||
with self.nodes[0].assert_debug_log( | |||||
["Misbehaving", | |||||
"peer=1 (0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avahello-signature"]): | |||||
bad_interface.send_avahello(interface_delegation_hex, wrong_key) | |||||
bad_interface.wait_for_disconnect() | |||||
self.log.info( | |||||
'Check that receiving a valid avahello triggers a proof getdata request') | |||||
proofid = good_interface.send_avahello( | |||||
interface_delegation_hex, delegated_key) | |||||
def getdata_found(): | |||||
with p2p_lock: | |||||
return good_interface.last_message.get( | |||||
"getdata") and good_interface.last_message["getdata"].inv[-1].hash == proofid | |||||
wait_until(getdata_found) | |||||
self.log.info('Check that we can download the proof from our peer') | |||||
node_proofid = FromHex(AvalancheProof(), proof).proofid | |||||
def wait_for_proof_validation(): | |||||
# Connect some blocks to trigger the proof verification | |||||
node.generate(2) | |||||
wait_until(lambda: node_proofid in get_proof_ids(node)) | |||||
wait_for_proof_validation() | |||||
getdata = msg_getdata([CInv(MSG_AVA_PROOF, node_proofid)]) | |||||
self.log.info( | |||||
"Proof has been inv'ed recently, check it can be requested") | |||||
good_interface.send_message(getdata) | |||||
def proof_received(peer): | |||||
with p2p_lock: | |||||
return peer.last_message.get( | |||||
"avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid | |||||
wait_until(lambda: proof_received(good_interface)) | |||||
# Restart the node | |||||
self.restart_node(0, self.extra_args[0] + [ | |||||
"-avaproof={}".format(proof), | |||||
"-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", | |||||
]) | |||||
wait_for_proof_validation() | |||||
self.log.info( | |||||
"The proof has not been announced, it cannot be requested") | |||||
peer = get_ava_p2p_interface(node, services=NODE_NETWORK) | |||||
peer.send_message(getdata) | |||||
# Give enough time for the node to answer. Since we cannot check for a | |||||
# non-event this is the best we can do | |||||
time.sleep(2) | |||||
assert not proof_received(peer) | |||||
self.log.info("The proof is known for long enough to be requested") | |||||
current_time = int(time.time()) | |||||
node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY) | |||||
peer.send_message(getdata) | |||||
wait_until(lambda: proof_received(peer)) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvalancheTest().main() | AvalancheTest().main() |