diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -5,7 +5,7 @@ """Test the resolution of forks via avalanche.""" import random -from test_framework.avatools import get_stakes +from test_framework.avatools import get_coinbase_stakes from test_framework.key import ( ECKey, ECPubKey, @@ -155,7 +155,7 @@ addrkey0 = node.get_deterministic_priv_key() blockhashes = node.generatetoaddress(100, addrkey0.address) # Use the first coinbase to create a stake - stakes = get_stakes(node, [blockhashes[0]], addrkey0.key) + stakes = get_coinbase_stakes(node, [blockhashes[0]], addrkey0.key) fork_node = self.nodes[1] # Make sure the fork node has synced the blocks diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -4,7 +4,10 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test building avalanche proofs and using them to add avalanche peers.""" -from test_framework.avatools import get_stakes +from test_framework.avatools import ( + get_coinbase_stakes, + get_stakes, +) from test_framework.key import ECKey, bytes_to_wif from test_framework.messages import AvalancheDelegation from test_framework.mininode import P2PInterface @@ -57,7 +60,7 @@ proof_expiration = 12 proof = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, - get_stakes(node, [blockhashes[0]], addrkey0.key)) + get_coinbase_stakes(node, [blockhashes[0]], addrkey0.key)) # Restart the node, making sure it is initially in IBD mode minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1 @@ -80,11 +83,9 @@ self.log.info( "A proof using the maximum number of stakes is accepted...") - blockhashes = node.generatetoaddress(AVALANCHE_MAX_PROOF_STAKES + 1, - addrkey0.address) - too_many_stakes = get_stakes(node, blockhashes, addrkey0.key) - maximum_stakes = get_stakes(node, blockhashes[:-1], addrkey0.key) + too_many_stakes = get_stakes(node, AVALANCHE_MAX_PROOF_STAKES + 1) + maximum_stakes = too_many_stakes[:-1] good_proof = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, maximum_stakes) @@ -171,11 +172,11 @@ dust = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, - get_stakes(node, [blockhashes[0]], addrkey0.key, amount="0")) + get_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, amount="0")) duplicate_stake = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, - get_stakes(node, [blockhashes[0]] * 2, addrkey0.key)) + get_coinbase_stakes(node, [blockhashes[0]] * 2, addrkey0.key)) bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348" "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085" diff --git a/test/functional/test_framework/avatools.py b/test/functional/test_framework/avatools.py --- a/test/functional/test_framework/avatools.py +++ b/test/functional/test_framework/avatools.py @@ -6,15 +6,22 @@ from typing import Any, Optional, List, Dict +from .messages import ( + CTransaction, + FromHex, + ToHex +) from .test_node import TestNode +from .util import satoshi_round -def get_stakes(node: TestNode, - blockhashes: List[str], - priv_key: str, - amount: Optional[str] = None) -> List[Dict[str, Any]]: +def get_coinbase_stakes(node: TestNode, + blockhashes: List[str], + priv_key: str, + amount: Optional[str] = None) -> List[Dict[str, Any]]: """Returns a list of dictionaries representing stakes, in a format - compatible with the buildavalancheproof RPC. + compatible with the buildavalancheproof RPC, using only coinbase + transactions. :param node: Test node used to get the block and coinbase data. :param blockhashes: List of block hashes, whose coinbase tx will be used @@ -41,3 +48,50 @@ 'iscoinbase': True, 'privatekey': priv_key, } for coinbase in coinbases] + + +def get_stakes(node: TestNode, count: int) -> List[Dict[str, Any]]: + """ + Generate and return a list of stakes by mining enough blocks and splitting + each coinbase transaction into 10 coins. + + This function can generate more valid stakes than `get_coinbase_stakes` + does, because on the regtest chain halving happens every 150 blocks so + the coinbase amount is below the dust threshold after only 900 blocks. + + :param node: Test node used to generate blocks and send transactions + :param count: Number of stakes to generate. + """ + node.generate(int(0.1 * count) + 101) + + utxos = node.listunspent() + addresses = [node.getnewaddress() for _ in range(10)] + + for i in range(count // 10 + 1): + u = utxos.pop() + inputs = [{"txid": u["txid"], "vout": u["vout"]}] + outputs = { + addr: satoshi_round(u['amount'] / 10) for addr in addresses} + raw_tx = node.createrawtransaction(inputs, outputs) + ctx = FromHex(CTransaction(), raw_tx) + ctx.vout[0].nValue -= node.calculate_fee(ctx) + signed_tx = node.signrawtransactionwithwallet(ToHex(ctx))["hex"] + node.sendrawtransaction(signed_tx) + + while node.getmempoolinfo()['size'] > 0: + node.generate(1) + + utxos = node.listunspent() + stakes = [] + for utxo in utxos: + blockhash = node.gettransaction(utxo["txid"])["blockhash"] + stakes.append({ + 'txid': utxo['txid'], + 'vout': utxo['vout'], + 'amount': utxo['amount'], + 'iscoinbase': utxo['label'] == "coinbase", + 'height': node.getblock(blockhash, 1)["height"], + 'privatekey': node.dumpprivkey(utxo["address"]), + }) + + return stakes[:count]