Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_p2p_avalanche.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2018 The Bitcoin developers | # Copyright (c) 2018 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 | ||||
from typing import List, Dict | |||||
from test_framework.key import ( | from test_framework.key import ( | ||||
ECKey, | ECKey, | ||||
ECPubKey, | ECPubKey, | ||||
) | ) | ||||
from test_framework.mininode import P2PInterface, mininode_lock | from test_framework.mininode import P2PInterface, mininode_lock | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
AvalancheResponse, | AvalancheResponse, | ||||
AvalancheVote, | AvalancheVote, | ||||
CInv, | CInv, | ||||
msg_avapoll, | msg_avapoll, | ||||
msg_tcpavaresponse, | msg_tcpavaresponse, | ||||
NODE_AVALANCHE, | NODE_AVALANCHE, | ||||
NODE_NETWORK, | NODE_NETWORK, | ||||
TCPAvalancheResponse, | TCPAvalancheResponse, | ||||
) | ) | ||||
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, | ||||
assert_raises_rpc_error, | |||||
wait_until, | wait_until, | ||||
) | ) | ||||
AVALANCHE_MAX_PROOF_STAKES = 1000 | |||||
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 | ||||
▲ Show 20 Lines • Show All 72 Lines • ▼ Show 20 Lines | def wait_for_avahello(self, timeout=5): | ||||
lambda: self.avahello is not None, | lambda: self.avahello is not None, | ||||
timeout=timeout, | timeout=timeout, | ||||
lock=mininode_lock) | lock=mininode_lock) | ||||
with mininode_lock: | with mininode_lock: | ||||
return self.avahello | return self.avahello | ||||
def get_stakes(coinbases: List[Dict], | |||||
priv_key: str) -> List[Dict]: | |||||
return [{ | |||||
'txid': coinbase['txid'], | |||||
'vout': coinbase['n'], | |||||
'amount': coinbase['value'], | |||||
'height': coinbase['height'], | |||||
'iscoinbase': True, | |||||
'privatekey': priv_key, | |||||
} for coinbase in coinbases] | |||||
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']] | ||||
self.supports_cli = False | self.supports_cli = False | ||||
def run_test(self): | def run_test(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
self.log.info("Check the node is signalling the avalanche service.") | self.log.info("Check the node is signalling the avalanche service.") | ||||
assert_equal( | assert_equal( | ||||
int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, | int(node.getnetworkinfo()['localservices'], 16) & NODE_AVALANCHE, | ||||
NODE_AVALANCHE) | NODE_AVALANCHE) | ||||
# Build a fake quorum of nodes. | # Build a fake quorum of nodes. | ||||
def get_quorum(): | |||||
def get_node(): | def get_node(): | ||||
n = TestNode() | n = TestNode() | ||||
node.add_p2p_connection( | node.add_p2p_connection( | ||||
n, services=NODE_NETWORK | NODE_AVALANCHE) | n, services=NODE_NETWORK | NODE_AVALANCHE) | ||||
n.wait_for_verack() | n.wait_for_verack() | ||||
# Get our own node id so we can use it later. | # Get our own node id so we can use it later. | ||||
n.nodeid = node.getpeerinfo()[-1]['id'] | n.nodeid = node.getpeerinfo()[-1]['id'] | ||||
return n | return n | ||||
def get_quorum(): | |||||
return [get_node() for _ in range(0, QUORUM_NODE_COUNT)] | return [get_node() for _ in range(0, QUORUM_NODE_COUNT)] | ||||
# Pick on node from the quorum for polling. | # Pick on node from the quorum for polling. | ||||
quorum = get_quorum() | quorum = get_quorum() | ||||
poll_node = quorum[0] | poll_node = quorum[0] | ||||
# Generate many block and poll for them. | # Generate many block and poll for them. | ||||
address = node.get_deterministic_priv_key().address | addrkey0 = node.get_deterministic_priv_key() | ||||
blocks = node.generatetoaddress(100, address) | blocks = node.generatetoaddress(100, addrkey0.address) | ||||
def get_coinbase(h): | def get_coinbase(h): | ||||
b = node.getblock(h, 2) | b = node.getblock(h, 2) | ||||
return { | return { | ||||
'height': b['height'], | 'height': b['height'], | ||||
'txid': b['tx'][0]['txid'], | 'txid': b['tx'][0]['txid'], | ||||
'n': 0, | 'n': 0, | ||||
'value': b['tx'][0]['vout'][0]['value'], | 'value': b['tx'][0]['vout'][0]['value'], | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
self.log.info("Trigger polling from the node...") | self.log.info("Trigger polling from the node...") | ||||
# duplicate the deterministic sig test from src/test/key_tests.cpp | # duplicate the deterministic sig test from src/test/key_tests.cpp | ||||
privkey = ECKey() | privkey = ECKey() | ||||
privkey.set(bytes.fromhex( | privkey.set(bytes.fromhex( | ||||
"12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) | "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) | ||||
pubkey = privkey.get_pubkey() | pubkey = privkey.get_pubkey() | ||||
privatekey = node.get_deterministic_priv_key().key | |||||
proof_sequence = 11 | proof_sequence = 11 | ||||
proof_expiration = 12 | proof_expiration = 12 | ||||
proof = node.buildavalancheproof( | proof = node.buildavalancheproof( | ||||
proof_sequence, proof_expiration, pubkey.get_bytes().hex(), | proof_sequence, proof_expiration, pubkey.get_bytes().hex(), | ||||
[{ | [{ | ||||
'txid': coinbases[0]['txid'], | 'txid': coinbases[0]['txid'], | ||||
'vout': coinbases[0]['n'], | 'vout': coinbases[0]['n'], | ||||
'amount': coinbases[0]['value'], | 'amount': coinbases[0]['value'], | ||||
'height': coinbases[0]['height'], | 'height': coinbases[0]['height'], | ||||
'iscoinbase': True, | 'iscoinbase': True, | ||||
'privatekey': privatekey, | 'privatekey': addrkey0.key, | ||||
}]) | }]) | ||||
# Activate the quorum. | # Activate the quorum. | ||||
for n in quorum: | for n in quorum: | ||||
success = node.addavalanchenode( | success = node.addavalanchenode( | ||||
n.nodeid, pubkey.get_bytes().hex(), proof) | n.nodeid, pubkey.get_bytes().hex(), proof) | ||||
assert success is True | assert success is True | ||||
▲ Show 20 Lines • Show All 100 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# Check the avahello is consistent | # Check the avahello is consistent | ||||
avahello = poll_node.wait_for_avahello().hello | avahello = poll_node.wait_for_avahello().hello | ||||
avakey.set(bytes.fromhex(node.getavalanchekey())) | avakey.set(bytes.fromhex(node.getavalanchekey())) | ||||
assert avakey.verify_schnorr( | assert avakey.verify_schnorr( | ||||
avahello.sig, avahello.get_sighash(poll_node)) | avahello.sig, avahello.get_sighash(poll_node)) | ||||
# Check the maximum number of stakes policy | |||||
blocks = node.generatetoaddress(AVALANCHE_MAX_PROOF_STAKES + 1, | |||||
addrkey0.address) | |||||
too_many_coinbases = [get_coinbase(h) for h in blocks] | |||||
too_many_stakes = get_stakes(too_many_coinbases, addrkey0.key) | |||||
self.log.info( | |||||
"A proof using the maximum number of stakes is accepted...") | |||||
maximum_stakes = get_stakes(too_many_coinbases[:-1], | |||||
addrkey0.key) | |||||
good_proof = node.buildavalancheproof( | |||||
proof_sequence, proof_expiration, | |||||
pubkey.get_bytes().hex(), maximum_stakes) | |||||
node.addavalanchenode( | |||||
get_node().nodeid, pubkey.get_bytes().hex(), good_proof) | |||||
self.log.info("A proof using too many stakes should be rejected...") | |||||
bad_proof = node.buildavalancheproof( | |||||
proof_sequence, proof_expiration, | |||||
pubkey.get_bytes().hex(), too_many_stakes) | |||||
assert_raises_rpc_error(-32602, "Avalanche proof has too many UTXOs", | |||||
node.addavalanchenode, | |||||
get_node().nodeid, pubkey.get_bytes().hex(), | |||||
bad_proof) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvalancheTest().main() | AvalancheTest().main() |