Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc_rpc_avalancheproof.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2021 The Bitcoin developers | # Copyright (c) 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 building avalanche proofs and using them to add avalanche peers.""" | """Test building avalanche proofs and using them to add avalanche peers.""" | ||||
import base64 | import base64 | ||||
from decimal import Decimal | from decimal import Decimal | ||||
from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE | from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE | ||||
from test_framework.avatools import ( | from test_framework.avatools import ( | ||||
create_coinbase_stakes, | create_coinbase_stakes, | ||||
create_stakes, | create_stakes, | ||||
get_proof_ids, | get_proof_ids, | ||||
wait_for_proof, | |||||
) | ) | ||||
from test_framework.key import ECKey, bytes_to_wif | from test_framework.key import ECKey, bytes_to_wif | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
AvalancheDelegation, | AvalancheDelegation, | ||||
AvalancheDelegationLevel, | AvalancheDelegationLevel, | ||||
AvalancheProof, | AvalancheProof, | ||||
FromHex, | FromHex, | ||||
) | ) | ||||
from test_framework.p2p import P2PInterface, p2p_lock | from test_framework.p2p import P2PInterface, p2p_lock | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.test_node import ErrorMatch | from test_framework.test_node import ErrorMatch | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
append_config, | append_config, | ||||
assert_equal, | assert_equal, | ||||
connect_nodes, | |||||
wait_until, | wait_until, | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
) | ) | ||||
AVALANCHE_MAX_PROOF_STAKES = 1000 | AVALANCHE_MAX_PROOF_STAKES = 1000 | ||||
PROOF_DUST_THRESHOLD = 1000000.0 | PROOF_DUST_THRESHOLD = 1000000.0 | ||||
"""Minimum amount per UTXO in a proof (in coins, not in satoshis)""" | """Minimum amount per UTXO in a proof (in coins, not in satoshis)""" | ||||
def add_interface_node(test_node) -> str: | def add_interface_node(test_node) -> str: | ||||
"""Create a mininode, connect it to test_node, return the nodeid | """Create a mininode, connect it to test_node, return the nodeid | ||||
of the mininode as registered by test_node. | of the mininode as registered by test_node. | ||||
""" | """ | ||||
n = P2PInterface() | n = P2PInterface() | ||||
test_node.add_p2p_connection(n) | test_node.add_p2p_connection(n) | ||||
n.wait_for_verack() | n.wait_for_verack() | ||||
return test_node.getpeerinfo()[-1]['id'] | return test_node.getpeerinfo()[-1]['id'] | ||||
class AvalancheProofTest(BitcoinTestFramework): | class AvalancheProofTest(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.extra_args = [['-enableavalanche=1', '-avacooldown=0'], ] | self.extra_args = [['-enableavalanche=1', '-avacooldown=0'], | ||||
['-enableavalanche=1', '-avacooldown=0']] | |||||
self.supports_cli = False | self.supports_cli = False | ||||
self.rpc_timeout = 120 | self.rpc_timeout = 120 | ||||
def run_test(self): | def run_test(self): | ||||
# Turn off node 1 while node 0 mines blocks to generate stakes, | |||||
# so that we can later try starting node 1 with an orphan proof. | |||||
self.stop_node(1) | |||||
node = self.nodes[0] | node = self.nodes[0] | ||||
addrkey0 = node.get_deterministic_priv_key() | addrkey0 = node.get_deterministic_priv_key() | ||||
blockhashes = node.generatetoaddress(100, addrkey0.address) | blockhashes = node.generatetoaddress(100, addrkey0.address) | ||||
self.log.info( | self.log.info( | ||||
"Make build a valid proof and restart the node to use it") | "Make build a valid proof and restart the node to use it") | ||||
privkey = ECKey() | privkey = ECKey() | ||||
Show All 31 Lines | def run_test(self): | ||||
# Invalid hex (odd number of hex digits) | # Invalid hex (odd number of hex digits) | ||||
assert_raises_rpc_error(-22, "Proof must be an hexadecimal string", | assert_raises_rpc_error(-22, "Proof must be an hexadecimal string", | ||||
node.decodeavalancheproof, proof[:-1]) | node.decodeavalancheproof, proof[:-1]) | ||||
# Valid hex but invalid proof | # Valid hex but invalid proof | ||||
assert_raises_rpc_error(-22, "Proof has invalid format", | assert_raises_rpc_error(-22, "Proof has invalid format", | ||||
node.decodeavalancheproof, proof[:-2]) | node.decodeavalancheproof, proof[:-2]) | ||||
# Restart the node, making sure it is initially in IBD mode | # Restart the node with this proof | ||||
minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1 | |||||
self.restart_node(0, self.extra_args[0] + [ | self.restart_node(0, self.extra_args[0] + [ | ||||
"-avaproof={}".format(proof), | "-avaproof={}".format(proof), | ||||
"-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", | "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", | ||||
"-minimumchainwork=0x{:x}".format(minchainwork), | |||||
]) | ]) | ||||
self.log.info( | self.log.info("The proof is registered at first chaintip update") | ||||
"The proof verification should be delayed until IBD is complete") | assert_equal(len(node.getavalanchepeerinfo()), 0) | ||||
assert node.getblockchaininfo()["initialblockdownload"] is True | |||||
# Our proof cannot be verified during IBD, so we should have no peer | |||||
assert not node.getavalanchepeerinfo() | |||||
# Mining one more block should cause us to leave IBD | |||||
node.generate(1) | node.generate(1) | ||||
# Our proof is now verified and our node is added as a peer | |||||
assert node.getblockchaininfo()["initialblockdownload"] is False | |||||
wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5) | wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5) | ||||
# This case will occur for users building proofs with a third party | |||||
# tool and then starting a new node that is not yet aware of the | |||||
# transactions used for stakes. | |||||
self.log.info("Start a node with an orphan proof") | |||||
self.start_node(1, self.extra_args[0] + [ | |||||
"-avaproof={}".format(proof), | |||||
"-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", | |||||
]) | |||||
# Mine a block to trigger an attempt at registering the proof | |||||
self.nodes[1].generate(1) | |||||
wait_for_proof(self.nodes[1], f"{proofobj.proofid:0{64}x}", | |||||
expect_orphan=True) | |||||
self.log.info("Connect to an up-to-date node to unorphan the proof") | |||||
connect_nodes(self.nodes[1], node) | |||||
node.generate(1) | |||||
Fabien: You need to sync the nodes (`self.sync_all()`) but I don't think the generate does anything… | |||||
wait_for_proof(self.nodes[1], f"{proofobj.proofid:0{64}x}", | |||||
expect_orphan=False) | |||||
if self.is_wallet_compiled(): | if self.is_wallet_compiled(): | ||||
self.log.info( | self.log.info( | ||||
"A proof using the maximum number of stakes is accepted...") | "A proof using the maximum number of stakes is accepted...") | ||||
new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1) | new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1) | ||||
# confirm the coinbase UTXOs | # confirm the coinbase UTXOs | ||||
node.generate(101) | node.generate(101) | ||||
too_many_stakes = create_stakes( | too_many_stakes = create_stakes( | ||||
▲ Show 20 Lines • Show All 303 Lines • Show Last 20 Lines |
You need to sync the nodes (self.sync_all()) but I don't think the generate does anything useful here