diff options
Diffstat (limited to 'test/functional')
59 files changed, 1000 insertions, 157 deletions
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 1a148f04f4..a4480307a7 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -31,6 +31,7 @@ Start three nodes: """ from test_framework.blocktools import ( + COINBASE_MATURITY, create_block, create_coinbase, ) @@ -161,8 +162,8 @@ class AssumeValidTest(BitcoinTestFramework): # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p0) - self.wait_until(lambda: self.nodes[0].getblockcount() >= 101) - assert_equal(self.nodes[0].getblockcount(), 101) + self.wait_until(lambda: self.nodes[0].getblockcount() >= COINBASE_MATURITY + 1) + assert_equal(self.nodes[0].getblockcount(), COINBASE_MATURITY + 1) # Send all blocks to node1. All blocks will be accepted. for i in range(2202): @@ -173,8 +174,8 @@ class AssumeValidTest(BitcoinTestFramework): # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p2) - self.wait_until(lambda: self.nodes[2].getblockcount() >= 101) - assert_equal(self.nodes[2].getblockcount(), 101) + self.wait_until(lambda: self.nodes[2].getblockcount() >= COINBASE_MATURITY + 1) + assert_equal(self.nodes[2].getblockcount(), COINBASE_MATURITY + 1) if __name__ == '__main__': diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index e6a53b52db..c712f7141c 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -22,6 +22,7 @@ needs an older patch version. import os import shutil +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import descsum_create @@ -64,13 +65,13 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.import_deterministic_coinbase_privkeys() def run_test(self): - self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress()) + self.nodes[0].generatetoaddress(COINBASE_MATURITY + 1, self.nodes[0].getnewaddress()) self.sync_blocks() # Sanity check the test framework: res = self.nodes[self.num_nodes - 1].getblockchaininfo() - assert_equal(res['blocks'], 101) + assert_equal(res['blocks'], COINBASE_MATURITY + 1) node_master = self.nodes[self.num_nodes - 5] node_v19 = self.nodes[self.num_nodes - 4] diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index d25aaa070d..10d2072dba 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -37,7 +37,7 @@ CLTV_HEIGHT = 1351 # Helper function to modify a transaction by # 1) prepending a given script to the scriptSig of vin 0 and # 2) (optionally) modify the nSequence of vin 0 and the tx's nLockTime -def cltv_modify_tx(node, tx, prepend_scriptsig, nsequence=None, nlocktime=None): +def cltv_modify_tx(tx, prepend_scriptsig, nsequence=None, nlocktime=None): assert_equal(len(tx.vin), 1) if nsequence is not None: tx.vin[0].nSequence = nsequence @@ -45,10 +45,9 @@ def cltv_modify_tx(node, tx, prepend_scriptsig, nsequence=None, nlocktime=None): tx.vin[0].scriptSig = CScript(prepend_scriptsig + list(CScript(tx.vin[0].scriptSig))) tx.rehash() - return tx -def cltv_invalidate(node, tx, failure_reason): +def cltv_invalidate(tx, failure_reason): # Modify the signature in vin 0 and nSequence/nLockTime of the tx to fail CLTV # # According to BIP65, OP_CHECKLOCKTIMEVERIFY can fail due the following reasons: @@ -69,14 +68,14 @@ def cltv_invalidate(node, tx, failure_reason): [[CScriptNum(500), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 500], ][failure_reason] - return cltv_modify_tx(node, tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) + cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) -def cltv_validate(node, tx, height): +def cltv_validate(tx, height): # Modify the signature in vin 0 and nSequence/nLockTime of the tx to pass CLTV scheme = [[CScriptNum(height), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, height] - return cltv_modify_tx(node, tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) + cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) class BIP65Test(BitcoinTestFramework): @@ -111,17 +110,17 @@ class BIP65Test(BitcoinTestFramework): self.log.info("Test that invalid-according-to-CLTV transactions can still appear in a block") # create one invalid tx per CLTV failure reason (5 in total) and collect them - invalid_ctlv_txs = [] + invalid_cltv_txs = [] for i in range(5): spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx'] - spendtx = cltv_invalidate(self.nodes[0], spendtx, i) - invalid_ctlv_txs.append(spendtx) + cltv_invalidate(spendtx, i) + invalid_cltv_txs.append(spendtx) tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1), block_time) block.nVersion = 3 - block.vtx.extend(invalid_ctlv_txs) + block.vtx.extend(invalid_cltv_txs) block.hashMerkleRoot = block.calc_merkle_root() block.solve() @@ -149,7 +148,7 @@ class BIP65Test(BitcoinTestFramework): # create and test one invalid tx per CLTV failure reason (5 in total) for i in range(5): spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx'] - spendtx = cltv_invalidate(self.nodes[0], spendtx, i) + cltv_invalidate(spendtx, i) expected_cltv_reject_reason = [ "non-mandatory-script-verify-flag (Operation not valid with the current stack size)", @@ -182,7 +181,7 @@ class BIP65Test(BitcoinTestFramework): peer.sync_with_ping() self.log.info("Test that a version 4 block with a valid-according-to-CLTV transaction is accepted") - spendtx = cltv_validate(self.nodes[0], spendtx, CLTV_HEIGHT - 1) + cltv_validate(spendtx, CLTV_HEIGHT - 1) block.vtx.pop(1) block.vtx.append(spendtx) diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index d3adde5cc5..cf270b25ee 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -12,6 +12,7 @@ the index. from decimal import Decimal from test_framework.blocktools import ( + COINBASE_MATURITY, create_block, create_coinbase, ) @@ -68,7 +69,7 @@ class CoinStatsIndexTest(BitcoinTestFramework): index_hash_options = ['none', 'muhash'] # Generate a normal transaction and mine it - node.generate(101) + node.generate(COINBASE_MATURITY + 1) address = self.nodes[0].get_deterministic_priv_key().address node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True) node.generate(1) diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py index 0a457ca17f..14f64d63a2 100755 --- a/test/functional/feature_loadblock.py +++ b/test/functional/feature_loadblock.py @@ -16,6 +16,7 @@ import sys import tempfile import urllib +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -28,7 +29,7 @@ class LoadblockTest(BitcoinTestFramework): def run_test(self): self.nodes[1].setnetworkactive(state=False) - self.nodes[0].generate(100) + self.nodes[0].generate(COINBASE_MATURITY) # Parsing the url of our node to get settings for config file data_dir = self.nodes[0].datadir diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index c7981d31dc..f467626801 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -14,15 +14,21 @@ Generate COINBASE_MATURITY (CB) more blocks to ensure the coinbases are mature. """ import time -from test_framework.blocktools import NORMAL_GBT_REQUEST_PARAMS, create_block, create_transaction, add_witness_commitment +from test_framework.blocktools import ( + COINBASE_MATURITY, + NORMAL_GBT_REQUEST_PARAMS, + add_witness_commitment, + create_block, + create_transaction, +) from test_framework.messages import CTransaction from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error -COINBASE_MATURITY = 100 NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)" + def trueDummy(tx): scriptSig = CScript(tx.vin[0].scriptSig) newscript = [] @@ -35,18 +41,17 @@ def trueDummy(tx): tx.vin[0].scriptSig = CScript(newscript) tx.rehash() -class NULLDUMMYTest(BitcoinTestFramework): +class NULLDUMMYTest(BitcoinTestFramework): def set_test_params(self): - # Need two nodes so GBT (getblocktemplate) doesn't complain that it's not connected. - self.num_nodes = 2 + self.num_nodes = 1 self.setup_clean_chain = True # This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through # normal segwit activation here (and don't use the default always-on behaviour). self.extra_args = [[ f'-segwitheight={COINBASE_MATURITY + 5}', '-addresstype=legacy', - ]] * 2 + ]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 8bee43b8ad..162814815e 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -147,13 +147,13 @@ class ProxyTest(BitcoinTestFramework): self.network_test(node, addr, network=NET_IPV6) if test_onion: - addr = "bitcoinostk4e4re.onion:8333" + addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333" self.log.debug("Test: outgoing onion connection through node for address {}".format(addr)) node.addnode(addr, "onetry") cmd = proxies[2].queue.get() assert isinstance(cmd, Socks5Command) assert_equal(cmd.atyp, AddressType.DOMAINNAME) - assert_equal(cmd.addr, b"bitcoinostk4e4re.onion") + assert_equal(cmd.addr, b"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion") assert_equal(cmd.port, 8333) if not auth: assert_equal(cmd.username, None) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 945880cc3b..0bb04ae267 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -6,6 +6,7 @@ from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut from test_framework.script import CScript, OP_DROP from test_framework.test_framework import BitcoinTestFramework @@ -27,7 +28,7 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): """ fee = 1*COIN while node.getbalance() < satoshi_round((amount + fee)/COIN): - node.generate(100) + node.generate(COINBASE_MATURITY) new_addr = node.getnewaddress() txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) @@ -115,6 +116,9 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.log.info("Running test prioritised transactions...") self.test_prioritised_transactions() + self.log.info("Running test no inherited signaling...") + self.test_no_inherited_signaling() + self.log.info("Passed") def test_simple_doublespend(self): @@ -563,5 +567,69 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(json0["vin"][0]["sequence"], 4294967293) assert_equal(json1["vin"][0]["sequence"], 4294967294) + def test_no_inherited_signaling(self): + # Send tx from which to conflict outputs later + base_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].generate(1) + self.sync_blocks() + + # Create an explicitly opt-in parent transaction + optin_parent_tx = self.nodes[0].createrawtransaction([{ + 'txid': base_txid, + 'vout': 0, + "sequence": 0xfffffffd, + }], {self.nodes[0].getnewaddress(): Decimal("9.99998")}) + + optin_parent_tx = self.nodes[0].signrawtransactionwithwallet(optin_parent_tx) + + # Broadcast parent tx + optin_parent_txid = self.nodes[0].sendrawtransaction(hexstring=optin_parent_tx["hex"], maxfeerate=0) + assert optin_parent_txid in self.nodes[0].getrawmempool() + + replacement_parent_tx = self.nodes[0].createrawtransaction([{ + 'txid': base_txid, + 'vout': 0, + "sequence": 0xfffffffd, + }], {self.nodes[0].getnewaddress(): Decimal("9.90000")}) + + replacement_parent_tx = self.nodes[0].signrawtransactionwithwallet(replacement_parent_tx) + + # Test if parent tx can be replaced. + res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']], maxfeerate=0)[0] + + # Parent can be replaced. + assert_equal(res['allowed'], True) + + # Create an opt-out child tx spending the opt-in parent + optout_child_tx = self.nodes[0].createrawtransaction([{ + 'txid': optin_parent_txid, + 'vout': 0, + "sequence": 0xffffffff, + }], {self.nodes[0].getnewaddress(): Decimal("9.99990")}) + + optout_child_tx = self.nodes[0].signrawtransactionwithwallet(optout_child_tx) + + # Broadcast child tx + optout_child_txid = self.nodes[0].sendrawtransaction(hexstring=optout_child_tx["hex"], maxfeerate=0) + assert optout_child_txid in self.nodes[0].getrawmempool() + + replacement_child_tx = self.nodes[0].createrawtransaction([{ + 'txid': optin_parent_txid, + 'vout': 0, + "sequence": 0xffffffff, + }], {self.nodes[0].getnewaddress(): Decimal("9.00000")}) + + replacement_child_tx = self.nodes[0].signrawtransactionwithwallet(replacement_child_tx) + + # Broadcast replacement child tx + # BIP 125 : + # 1. The original transactions signal replaceability explicitly or through inheritance as described in the above + # Summary section. + # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does. + # The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction. + # See CVE-2021-31876 for further explanations. + assert optin_parent_txid in self.nodes[0].getrawmempool() + assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0) + if __name__ == '__main__': ReplaceByFeeTest().main() diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 183a43abd4..fc04853199 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -5,6 +5,7 @@ # Test Taproot softfork (BIPs 340-342) from test_framework.blocktools import ( + COINBASE_MATURITY, create_coinbase, create_block, add_witness_commitment, @@ -1440,7 +1441,7 @@ class TaprootTest(BitcoinTestFramework): def run_test(self): # Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot). self.log.info("Post-activation tests...") - self.nodes[1].generate(101) + self.nodes[1].generate(COINBASE_MATURITY + 1) self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3]) # Re-connect nodes in case they have been disconnected diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index b5ce18a48b..30cd499b3f 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -5,6 +5,8 @@ """Test bitcoin-cli""" from decimal import Decimal + +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -16,7 +18,7 @@ from test_framework.util import ( # The block reward of coinbaseoutput.nValue (50) BTC/block matures after # COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect # node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block. -BLOCKS = 101 +BLOCKS = COINBASE_MATURITY + 1 BALANCE = (BLOCKS - 100) * 50 JSON_PARSING_ERROR = 'error: Error parsing JSON: foo' diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index c4002f524a..12aac3ab65 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -67,7 +67,8 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) - assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) + assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26)) + assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index eb08765ebf..6de6778909 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -15,6 +15,7 @@ Only v0.15.2 is required by this test. The rest is used in other backwards compa import os +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.wallet import MiniWallet @@ -41,7 +42,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): old_node, new_node = self.nodes new_wallet = MiniWallet(new_node) new_wallet.generate(1) - new_node.generate(100) + new_node.generate(COINBASE_MATURITY) # Sync the nodes to ensure old_node has the block that contains the coinbase that new_wallet will spend. # Otherwise, because coinbases are only valid in a block and not as loose txns, if the nodes aren't synced # unbroadcasted_tx won't pass old_node's `MemPoolAccept::PreChecks`. diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py index 4c46075ae9..7d1bfef333 100755 --- a/test/functional/mempool_expiry.py +++ b/test/functional/mempool_expiry.py @@ -12,6 +12,7 @@ definable expiry timeout via the '-mempoolexpiry=<n>' command line argument from datetime import timedelta +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -36,7 +37,7 @@ class MempoolExpiryTest(BitcoinTestFramework): # Add enough mature utxos to the wallet so that all txs spend confirmed coins. self.wallet.generate(4) - node.generate(100) + node.generate(COINBASE_MATURITY) # Send a parent transaction that will expire. parent_txid = self.wallet.send_self_transfer(from_node=node)['txid'] diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index 884a2fef11..1e9895e621 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -9,6 +9,7 @@ from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round @@ -42,7 +43,7 @@ class MempoolPackagesTest(BitcoinTestFramework): def run_test(self): # Mine some blocks and have them mature. - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 461f9237ff..606717d890 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -6,6 +6,7 @@ from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import COIN from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework @@ -59,7 +60,7 @@ class MempoolPackagesTest(BitcoinTestFramework): def run_test(self): # Mine some blocks and have them mature. peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 8e1f87e42c..bcc6aa7bcc 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -8,10 +8,9 @@ Test re-org scenarios with a mempool that contains transactions that spend (directly or indirectly) coinbase transactions. """ -from test_framework.blocktools import create_raw_transaction from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error - +from test_framework.wallet import MiniWallet class MempoolCoinbaseTest(BitcoinTestFramework): def set_test_params(self): @@ -23,86 +22,90 @@ class MempoolCoinbaseTest(BitcoinTestFramework): [] ] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): + wallet = MiniWallet(self.nodes[0]) + # Start with a 200 block chain assert_equal(self.nodes[0].getblockcount(), 200) - # Mine four blocks. After this, nodes[0] blocks - # 101, 102, and 103 are spend-able. - new_blocks = self.nodes[1].generate(4) - self.sync_all() - - node0_address = self.nodes[0].getnewaddress() - node1_address = self.nodes[1].getnewaddress() + self.log.info("Add 4 coinbase utxos to the miniwallet") + # Block 76 contains the first spendable coinbase txs. + first_block = 76 + wallet.scan_blocks(start=first_block, num=4) # Three scenarios for re-orging coinbase spends in the memory pool: - # 1. Direct coinbase spend : spend_101 - # 2. Indirect (coinbase spend in chain, child in mempool) : spend_102 and spend_102_1 - # 3. Indirect (coinbase and child both in chain) : spend_103 and spend_103_1 - # Use invalidatblock to make all of the above coinbase spends invalid (immature coinbase), + # 1. Direct coinbase spend : spend_1 + # 2. Indirect (coinbase spend in chain, child in mempool) : spend_2 and spend_2_1 + # 3. Indirect (coinbase and child both in chain) : spend_3 and spend_3_1 + # Use invalidateblock to make all of the above coinbase spends invalid (immature coinbase), # and make sure the mempool code behaves correctly. - b = [self.nodes[0].getblockhash(n) for n in range(101, 105)] + b = [self.nodes[0].getblockhash(n) for n in range(first_block, first_block+4)] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] - spend_101_raw = create_raw_transaction(self.nodes[0], coinbase_txids[1], node1_address, amount=49.99) - spend_102_raw = create_raw_transaction(self.nodes[0], coinbase_txids[2], node0_address, amount=49.99) - spend_103_raw = create_raw_transaction(self.nodes[0], coinbase_txids[3], node0_address, amount=49.99) - - # Create a transaction which is time-locked to two blocks in the future - timelock_tx = self.nodes[0].createrawtransaction( - inputs=[{ - "txid": coinbase_txids[0], - "vout": 0, - }], - outputs={node0_address: 49.99}, - locktime=self.nodes[0].getblockcount() + 2, - ) - timelock_tx = self.nodes[0].signrawtransactionwithwallet(timelock_tx)["hex"] - # This will raise an exception because the timelock transaction is too immature to spend + utxo_1 = wallet.get_utxo(txid=coinbase_txids[1]) + utxo_2 = wallet.get_utxo(txid=coinbase_txids[2]) + utxo_3 = wallet.get_utxo(txid=coinbase_txids[3]) + self.log.info("Create three transactions spending from coinbase utxos: spend_1, spend_2, spend_3") + spend_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_1) + spend_2 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_2) + spend_3 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_3) + + self.log.info("Create another transaction which is time-locked to two blocks in the future") + utxo = wallet.get_utxo(txid=coinbase_txids[0]) + timelock_tx = wallet.create_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=utxo, + mempool_valid=False, + locktime=self.nodes[0].getblockcount() + 2 + )['hex'] + + self.log.info("Check that the time-locked transaction is too immature to spend") assert_raises_rpc_error(-26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx) - # Broadcast and mine spend_102 and 103: - spend_102_id = self.nodes[0].sendrawtransaction(spend_102_raw) - spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw) + self.log.info("Broadcast and mine spend_2 and spend_3") + wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_2['hex']) + wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_3['hex']) + self.log.info("Generate a block") self.nodes[0].generate(1) - # Time-locked transaction is still too immature to spend + self.log.info("Check that time-locked transaction is still too immature to spend") assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx) - # Create 102_1 and 103_1: - spend_102_1_raw = create_raw_transaction(self.nodes[0], spend_102_id, node1_address, amount=49.98) - spend_103_1_raw = create_raw_transaction(self.nodes[0], spend_103_id, node1_address, amount=49.98) + self.log.info("Create spend_2_1 and spend_3_1") + spend_2_utxo = wallet.get_utxo(txid=spend_2['txid']) + spend_2_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_2_utxo) + spend_3_utxo = wallet.get_utxo(txid=spend_3['txid']) + spend_3_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_3_utxo) - # Broadcast and mine 103_1: - spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw) + self.log.info("Broadcast and mine spend_3_1") + spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) + self.log.info("Generate a block") last_block = self.nodes[0].generate(1) # Sync blocks, so that peer 1 gets the block before timelock_tx # Otherwise, peer 1 would put the timelock_tx in recentRejects self.sync_all() - # Time-locked transaction can now be spent + self.log.info("The time-locked transaction can now be spent") timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) - # ... now put spend_101 and spend_102_1 in memory pools: - spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw) - spend_102_1_id = self.nodes[0].sendrawtransaction(spend_102_1_raw) + self.log.info("Add spend_1 and spend_2_1 to the mempool") + spend_1_id = self.nodes[0].sendrawtransaction(spend_1['hex']) + spend_2_1_id = self.nodes[0].sendrawtransaction(spend_2_1['hex']) - assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, timelock_tx_id}) + assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, timelock_tx_id}) self.sync_all() + self.log.info("invalidate the last block") for node in self.nodes: node.invalidateblock(last_block[0]) - # Time-locked transaction is now too immature and has been removed from the mempool - # spend_103_1 has been re-orged out of the chain and is back in the mempool - assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, spend_103_1_id}) + self.log.info("The time-locked transaction is now too immature and has been removed from the mempool") + self.log.info("spend_3_1 has been re-orged out of the chain and is back in the mempool") + assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, spend_3_1_id}) - # Use invalidateblock to re-org back and make all those coinbase spends - # immature/invalid: + self.log.info("Use invalidateblock to re-org back and make all those coinbase spends immature/invalid") + b = self.nodes[0].getblockhash(first_block + 100) for node in self.nodes: - node.invalidateblock(new_blocks[0]) + node.invalidateblock(b) - # mempool should be empty. + self.log.info("Check that the mempool is empty") assert_equal(set(self.nodes[0].getrawmempool()), set()) self.sync_all() diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py index 4aa58270b6..1b5ca7e15a 100755 --- a/test/functional/mempool_resurrect.py +++ b/test/functional/mempool_resurrect.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test resurrection of mined transactions when the blockchain is re-organized.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.wallet import MiniWallet @@ -20,7 +21,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework): # Add enough mature utxos to the wallet so that all txs spend confirmed coins wallet.generate(3) - node.generate(100) + node.generate(COINBASE_MATURITY) # Spend block 1/2/3's coinbase transactions # Mine a block diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py index cc32f78e2e..715b68e04c 100755 --- a/test/functional/mining_getblocktemplate_longpoll.py +++ b/test/functional/mining_getblocktemplate_longpoll.py @@ -8,6 +8,7 @@ from decimal import Decimal import random import threading +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import get_rpc_proxy from test_framework.wallet import MiniWallet @@ -62,7 +63,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework): assert not thr.is_alive() # Add enough mature utxos to the wallets, so that all txs spend confirmed coins - self.nodes[0].generate(100) + self.nodes[0].generate(COINBASE_MATURITY) self.sync_blocks() self.log.info("Test that introducing a new transaction into the mempool will terminate the longpoll") diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index ab2556cd72..6409d4ea82 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -6,6 +6,7 @@ import time +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import msg_tx from test_framework.p2p import P2PInterface, P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework @@ -23,7 +24,7 @@ class P2PBlocksOnly(BitcoinTestFramework): self.miniwallet = MiniWallet(self.nodes[0]) # Add enough mature utxos to the wallet, so that all txs spend confirmed coins self.miniwallet.generate(2) - self.nodes[0].generate(100) + self.nodes[0].generate(COINBASE_MATURITY) self.blocksonly_mode_tests() self.blocks_relay_conn_tests() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 55573efc06..3e4f2f974d 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -9,7 +9,12 @@ Version 2 compact blocks are post-segwit (wtxids) """ import random -from test_framework.blocktools import create_block, NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment +from test_framework.blocktools import ( + COINBASE_MATURITY, + NORMAL_GBT_REQUEST_PARAMS, + add_witness_commitment, + create_block, +) from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex from test_framework.p2p import p2p_lock, P2PInterface from test_framework.script import CScript, OP_TRUE, OP_DROP @@ -115,7 +120,7 @@ class CompactBlocksTest(BitcoinTestFramework): block = self.build_block_on_tip(self.nodes[0]) self.segwit_node.send_and_ping(msg_no_witness_block(block)) assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 - self.nodes[0].generatetoaddress(100, self.nodes[0].getnewaddress(address_type="bech32")) + self.nodes[0].generatetoaddress(COINBASE_MATURITY, self.nodes[0].getnewaddress(address_type="bech32")) total_value = block.vtx[0].vout[0].nValue out_value = total_value // 10 @@ -226,7 +231,7 @@ class CompactBlocksTest(BitcoinTestFramework): # This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. def test_invalid_cmpctblock_message(self): - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) block = self.build_block_on_tip(self.nodes[0]) cmpct_block = P2PHeaderAndShortIDs() @@ -244,7 +249,7 @@ class CompactBlocksTest(BitcoinTestFramework): version = test_node.cmpct_version node = self.nodes[0] # Generate a bunch of transactions. - node.generate(101) + node.generate(COINBASE_MATURITY + 1) num_transactions = 25 address = node.getnewaddress() diff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py index d60aa5b383..a525996493 100755 --- a/test/functional/p2p_eviction.py +++ b/test/functional/p2p_eviction.py @@ -15,7 +15,11 @@ Therefore, this test is limited to the remaining protection criteria. import time -from test_framework.blocktools import create_block, create_coinbase +from test_framework.blocktools import ( + COINBASE_MATURITY, + create_block, + create_coinbase, +) from test_framework.messages import CTransaction, FromHex, msg_pong, msg_tx from test_framework.p2p import P2PDataStore, P2PInterface from test_framework.test_framework import BitcoinTestFramework @@ -45,7 +49,7 @@ class P2PEvict(BitcoinTestFramework): protected_peers = set() # peers that we expect to be protected from eviction current_peer = -1 node = self.nodes[0] - node.generatetoaddress(101, node.get_deterministic_priv_key().address) + node.generatetoaddress(COINBASE_MATURITY + 1, node.get_deterministic_priv_key().address) self.log.info("Create 4 peers and protect them from eviction by sending us a block") for _ in range(4): diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index 52dc4de3bd..0175b9f6c0 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -6,6 +6,7 @@ from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import MSG_TX, MSG_WTX, msg_feefilter from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework @@ -81,7 +82,7 @@ class FeeFilterTest(BitcoinTestFramework): miniwallet = MiniWallet(node1) # Add enough mature utxos to the wallet, so that all txs spend confirmed coins miniwallet.generate(5) - node1.generate(100) + node1.generate(COINBASE_MATURITY) conn = self.nodes[0].add_p2p_connection(TestP2PConn()) diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index 71d5ca92b3..f1538e8ac7 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -29,6 +29,8 @@ from test_framework.util import ( assert_greater_than_or_equal, ) +PEER_TIMEOUT = 3 + class LazyPeer(P2PInterface): def __init__(self): @@ -98,7 +100,7 @@ class P2PVersionStore(P2PInterface): class P2PLeakTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-peertimeout=4']] + self.extra_args = [[f"-peertimeout={PEER_TIMEOUT}"]] def create_old_version(self, nversion): old_version_msg = msg_version() @@ -134,7 +136,7 @@ class P2PLeakTest(BitcoinTestFramework): self.nodes[0].generate(nblocks=1) # Give the node enough time to possibly leak out a message - time.sleep(5) + time.sleep(PEER_TIMEOUT + 2) # Make sure only expected messages came in assert not no_version_idle_peer.unexpected_msg diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index a45f792e81..9a4ceb86ae 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that we don't leak txs to inbound peers that we haven't yet announced to""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import msg_getdata, CInv, MSG_TX from test_framework.p2p import p2p_lock, P2PDataStore from test_framework.test_framework import BitcoinTestFramework @@ -27,7 +28,7 @@ class P2PLeakTxTest(BitcoinTestFramework): miniwallet = MiniWallet(gen_node) # Add enough mature utxos to the wallet, so that all txs spend confirmed coins miniwallet.generate(1) - gen_node.generate(100) + gen_node.generate(COINBASE_MATURITY) inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 19f0d5765a..af515f3a27 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -9,6 +9,7 @@ import itertools import json import os +from test_framework.blocktools import COINBASE_MATURITY from test_framework.authproxy import JSONRPCException from test_framework.descriptors import descsum_create, drop_origins from test_framework.key import ECPubKey, ECKey @@ -109,7 +110,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): def checkbalances(self): node0, node1, node2 = self.nodes - node0.generate(100) + node0.generate(COINBASE_MATURITY) self.sync_all() bal0 = node0.getbalance() diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py index dc469ba552..3efbdab013 100755 --- a/test/functional/rpc_dumptxoutset.py +++ b/test/functional/rpc_dumptxoutset.py @@ -4,6 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the generation of UTXO snapshots using `dumptxoutset`. """ + +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error @@ -21,7 +23,7 @@ class DumptxoutsetTest(BitcoinTestFramework): node = self.nodes[0] mocktime = node.getblockheader(node.getblockhash(0))['time'] + 1 node.setmocktime(mocktime) - node.generate(100) + node.generate(COINBASE_MATURITY) FILENAME = 'txoutset.dat' out = node.dumptxoutset(FILENAME) diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index 57794ae973..4af518c870 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -6,6 +6,8 @@ # # Test getblockstats rpc call # + +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -41,7 +43,7 @@ class GetblockstatsTest(BitcoinTestFramework): def generate_test_data(self, filename): mocktime = 1525107225 self.nodes[0].setmocktime(mocktime) - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) address = self.nodes[0].get_deterministic_priv_key().address self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=True) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 9a2817a6ee..6e5ef770d1 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -11,6 +11,7 @@ from decimal import Decimal from itertools import product import time +from test_framework.blocktools import COINBASE_MATURITY from test_framework.p2p import P2PInterface import test_framework.messages from test_framework.messages import ( @@ -53,7 +54,7 @@ class NetTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.wallet.generate(1) # Get out of IBD for the minfeefilter and getpeerinfo tests. - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) # By default, the test framework sets up an addnode connection from # node 1 --> node0. By connecting node0 --> node 1, we're left with diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py new file mode 100755 index 0000000000..3d8d81d6b8 --- /dev/null +++ b/test/functional/rpc_packages.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""RPCs that handle raw transaction packages.""" + +from decimal import Decimal +from io import BytesIO +import random + +from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE +from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + BIP125_SEQUENCE_NUMBER, + COIN, + CTransaction, + CTxInWitness, +) +from test_framework.script import ( + CScript, + OP_TRUE, +) +from test_framework.util import ( + assert_equal, + hex_str_to_bytes, +) + +class RPCPackagesTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def assert_testres_equal(self, package_hex, testres_expected): + """Shuffle package_hex and assert that the testmempoolaccept result matches testres_expected. This should only + be used to test packages where the order does not matter. The ordering of transactions in package_hex and + testres_expected must match. + """ + shuffled_indeces = list(range(len(package_hex))) + random.shuffle(shuffled_indeces) + shuffled_package = [package_hex[i] for i in shuffled_indeces] + shuffled_testres = [testres_expected[i] for i in shuffled_indeces] + assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package)) + + def run_test(self): + self.log.info("Generate blocks to create UTXOs") + node = self.nodes[0] + self.privkeys = [node.get_deterministic_priv_key().key] + self.address = node.get_deterministic_priv_key().address + self.coins = [] + # The last 100 coinbase transactions are premature + for b in node.generatetoaddress(200, self.address)[:100]: + coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] + self.coins.append({ + "txid": coinbase["txid"], + "amount": coinbase["vout"][0]["value"], + "scriptPubKey": coinbase["vout"][0]["scriptPubKey"], + }) + + # Create some transactions that can be reused throughout the test. Never submit these to mempool. + self.independent_txns_hex = [] + self.independent_txns_testres = [] + for _ in range(3): + coin = self.coins.pop() + rawtx = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], + {self.address : coin["amount"] - Decimal("0.0001")}) + signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) + assert signedtx["complete"] + testres = node.testmempoolaccept([signedtx["hex"]]) + assert testres[0]["allowed"] + self.independent_txns_hex.append(signedtx["hex"]) + # testmempoolaccept returns a list of length one, avoid creating a 2D list + self.independent_txns_testres.append(testres[0]) + self.independent_txns_testres_blank = [{ + "txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres] + + self.test_independent() + self.test_chain() + self.test_multiple_children() + self.test_multiple_parents() + self.test_conflicting() + self.test_rbf() + + def chain_transaction(self, parent_txid, parent_value, n=0, parent_locking_script=None): + """Build a transaction that spends parent_txid.vout[n] and produces one output with + amount = parent_value with a fee deducted. + Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created). + """ + node = self.nodes[0] + inputs = [{"txid": parent_txid, "vout": n}] + my_value = parent_value - Decimal("0.0001") + outputs = {self.address : my_value} + rawtx = node.createrawtransaction(inputs, outputs) + prevtxs = [{ + "txid": parent_txid, + "vout": n, + "scriptPubKey": parent_locking_script, + "amount": parent_value, + }] if parent_locking_script else None + signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys, prevtxs=prevtxs) + tx = CTransaction() + assert signedtx["complete"] + tx.deserialize(BytesIO(hex_str_to_bytes(signedtx["hex"]))) + return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex()) + + def test_independent(self): + self.log.info("Test multiple independent transactions in a package") + node = self.nodes[0] + # For independent transactions, order doesn't matter. + self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres) + + self.log.info("Test an otherwise valid package with an extra garbage tx appended") + garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {self.address: 1}) + tx = CTransaction() + tx.deserialize(BytesIO(hex_str_to_bytes(garbage_tx))) + # Only the txid and wtxids are returned because validation is incomplete for the independent txns. + # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package, + # it terminates immediately to avoid unnecessary, expensive signature verification. + package_bad = self.independent_txns_hex + [garbage_tx] + testres_bad = self.independent_txns_testres_blank + [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "allowed": False, "reject-reason": "missing-inputs"}] + self.assert_testres_equal(package_bad, testres_bad) + + self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully") + coin = self.coins.pop() + tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], + {self.address : coin["amount"] - Decimal("0.0001")}) + tx_bad_sig = CTransaction() + tx_bad_sig.deserialize(BytesIO(hex_str_to_bytes(tx_bad_sig_hex))) + testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex]) + # By the time the signature for the last transaction is checked, all the other transactions + # have been fully validated, which is why the node returns full validation results for all + # transactions here but empty results in other cases. + assert_equal(testres_bad_sig, self.independent_txns_testres + [{ + "txid": tx_bad_sig.rehash(), + "wtxid": tx_bad_sig.getwtxid(), "allowed": False, + "reject-reason": "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)" + }]) + + self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate") + coin = self.coins.pop() + tx_high_fee_raw = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], + {self.address : coin["amount"] - Decimal("0.999")}) + tx_high_fee_signed = node.signrawtransactionwithkey(hexstring=tx_high_fee_raw, privkeys=self.privkeys) + assert tx_high_fee_signed["complete"] + tx_high_fee = CTransaction() + tx_high_fee.deserialize(BytesIO(hex_str_to_bytes(tx_high_fee_signed["hex"]))) + testres_high_fee = node.testmempoolaccept([tx_high_fee_signed["hex"]]) + assert_equal(testres_high_fee, [ + {"txid": tx_high_fee.rehash(), "wtxid": tx_high_fee.getwtxid(), "allowed": False, "reject-reason": "max-fee-exceeded"} + ]) + package_high_fee = [tx_high_fee_signed["hex"]] + self.independent_txns_hex + testres_package_high_fee = node.testmempoolaccept(package_high_fee) + assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank) + + def test_chain(self): + node = self.nodes[0] + first_coin = self.coins.pop() + + # Chain of 25 transactions + parent_locking_script = None + txid = first_coin["txid"] + chain_hex = [] + chain_txns = [] + value = first_coin["amount"] + + for _ in range(25): + (tx, txhex, value, parent_locking_script) = self.chain_transaction(txid, value, 0, parent_locking_script) + txid = tx.rehash() + chain_hex.append(txhex) + chain_txns.append(tx) + + self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency") + assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]), + [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]]) + + self.log.info("Testmempoolaccept a chain of 25 transactions") + testres_multiple = node.testmempoolaccept(rawtxs=chain_hex) + + testres_single = [] + # Test accept and then submit each one individually, which should be identical to package test accept + for rawtx in chain_hex: + testres = node.testmempoolaccept([rawtx]) + testres_single.append(testres[0]) + # Submit the transaction now so its child should have no problem validating + node.sendrawtransaction(rawtx) + assert_equal(testres_single, testres_multiple) + + # Clean up by clearing the mempool + node.generate(1) + + def test_multiple_children(self): + node = self.nodes[0] + + self.log.info("Testmempoolaccept a package in which a transaction has two children within the package") + first_coin = self.coins.pop() + value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs + inputs = [{"txid": first_coin["txid"], "vout": 0}] + outputs = [{self.address : value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : value}] + rawtx = node.createrawtransaction(inputs, outputs) + + parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) + parent_tx = CTransaction() + assert parent_signed["complete"] + parent_tx.deserialize(BytesIO(hex_str_to_bytes(parent_signed["hex"]))) + parent_txid = parent_tx.rehash() + assert node.testmempoolaccept([parent_signed["hex"]])[0]["allowed"] + + parent_locking_script_a = parent_tx.vout[0].scriptPubKey.hex() + child_value = value - Decimal("0.0001") + + # Child A + (_, tx_child_a_hex, _, _) = self.chain_transaction(parent_txid, child_value, 0, parent_locking_script_a) + assert not node.testmempoolaccept([tx_child_a_hex])[0]["allowed"] + + # Child B + rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : child_value}) + tx_child_b = CTransaction() + tx_child_b.deserialize(BytesIO(hex_str_to_bytes(rawtx_b))) + tx_child_b.wit.vtxinwit = [CTxInWitness()] + tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx_child_b_hex = tx_child_b.serialize().hex() + assert not node.testmempoolaccept([tx_child_b_hex])[0]["allowed"] + + self.log.info("Testmempoolaccept with entire package, should work with children in either order") + testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]) + testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_b_hex, tx_child_a_hex]) + assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba]) + + testres_single = [] + # Test accept and then submit each one individually, which should be identical to package testaccept + for rawtx in [parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]: + testres = node.testmempoolaccept([rawtx]) + testres_single.append(testres[0]) + # Submit the transaction now so its child should have no problem validating + node.sendrawtransaction(rawtx) + assert_equal(testres_single, testres_multiple_ab) + + def create_child_with_parents(self, parents_tx, values, locking_scripts): + """Creates a transaction that spends the first output of each parent in parents_tx.""" + num_parents = len(parents_tx) + total_value = sum(values) + inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx] + outputs = {self.address : total_value - num_parents * Decimal("0.0001")} + rawtx_child = self.nodes[0].createrawtransaction(inputs, outputs) + prevtxs = [] + for i in range(num_parents): + prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]}) + signedtx_child = self.nodes[0].signrawtransactionwithkey(hexstring=rawtx_child, privkeys=self.privkeys, prevtxs=prevtxs) + assert signedtx_child["complete"] + return signedtx_child["hex"] + + def test_multiple_parents(self): + node = self.nodes[0] + + self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package") + for num_parents in [2, 10, 24]: + # Test a package with num_parents parents and 1 child transaction. + package_hex = [] + parents_tx = [] + values = [] + parent_locking_scripts = [] + for _ in range(num_parents): + parent_coin = self.coins.pop() + value = parent_coin["amount"] + (tx, txhex, value, parent_locking_script) = self.chain_transaction(parent_coin["txid"], value) + package_hex.append(txhex) + parents_tx.append(tx) + values.append(value) + parent_locking_scripts.append(parent_locking_script) + child_hex = self.create_child_with_parents(parents_tx, values, parent_locking_scripts) + # Package accept should work with the parents in any order (as long as parents come before child) + for _ in range(10): + random.shuffle(package_hex) + testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_hex]) + assert all([testres["allowed"] for testres in testres_multiple]) + + testres_single = [] + # Test accept and then submit each one individually, which should be identical to package testaccept + for rawtx in package_hex + [child_hex]: + testres_single.append(node.testmempoolaccept([rawtx])[0]) + # Submit the transaction now so its child should have no problem validating + node.sendrawtransaction(rawtx) + assert_equal(testres_single, testres_multiple) + + def test_conflicting(self): + node = self.nodes[0] + prevtx = self.coins.pop() + inputs = [{"txid": prevtx["txid"], "vout": 0}] + output1 = {node.get_deterministic_priv_key().address: 50 - 0.00125} + output2 = {ADDRESS_BCRT1_P2WSH_OP_TRUE: 50 - 0.00125} + + # tx1 and tx2 share the same inputs + rawtx1 = node.createrawtransaction(inputs, output1) + rawtx2 = node.createrawtransaction(inputs, output2) + signedtx1 = node.signrawtransactionwithkey(hexstring=rawtx1, privkeys=self.privkeys) + signedtx2 = node.signrawtransactionwithkey(hexstring=rawtx2, privkeys=self.privkeys) + tx1 = CTransaction() + tx1.deserialize(BytesIO(hex_str_to_bytes(signedtx1["hex"]))) + tx2 = CTransaction() + tx2.deserialize(BytesIO(hex_str_to_bytes(signedtx2["hex"]))) + assert signedtx1["complete"] + assert signedtx2["complete"] + + # Ensure tx1 and tx2 are valid by themselves + assert node.testmempoolaccept([signedtx1["hex"]])[0]["allowed"] + assert node.testmempoolaccept([signedtx2["hex"]])[0]["allowed"] + + self.log.info("Test duplicate transactions in the same package") + testres = node.testmempoolaccept([signedtx1["hex"], signedtx1["hex"]]) + assert_equal(testres, [ + {"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"}, + {"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"} + ]) + + self.log.info("Test conflicting transactions in the same package") + testres = node.testmempoolaccept([signedtx1["hex"], signedtx2["hex"]]) + assert_equal(testres, [ + {"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"}, + {"txid": tx2.rehash(), "wtxid": tx2.getwtxid(), "package-error": "conflict-in-package"} + ]) + + def test_rbf(self): + node = self.nodes[0] + coin = self.coins.pop() + inputs = [{"txid": coin["txid"], "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}] + fee = Decimal('0.00125000') + output = {node.get_deterministic_priv_key().address: 50 - fee} + raw_replaceable_tx = node.createrawtransaction(inputs, output) + signed_replaceable_tx = node.signrawtransactionwithkey(hexstring=raw_replaceable_tx, privkeys=self.privkeys) + testres_replaceable = node.testmempoolaccept([signed_replaceable_tx["hex"]]) + replaceable_tx = CTransaction() + replaceable_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) + assert_equal(testres_replaceable, [ + {"txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), + "allowed": True, "vsize": replaceable_tx.get_vsize(), "fees": { "base": fee }} + ]) + + # Replacement transaction is identical except has double the fee + replacement_tx = CTransaction() + replacement_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) + replacement_tx.vout[0].nValue -= int(fee * COIN) # Doubled fee + signed_replacement_tx = node.signrawtransactionwithkey(replacement_tx.serialize().hex(), self.privkeys) + replacement_tx.deserialize(BytesIO(hex_str_to_bytes(signed_replacement_tx["hex"]))) + + self.log.info("Test that transactions within a package cannot replace each other") + testres_rbf_conflicting = node.testmempoolaccept([signed_replaceable_tx["hex"], signed_replacement_tx["hex"]]) + assert_equal(testres_rbf_conflicting, [ + {"txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), "package-error": "conflict-in-package"}, + {"txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "package-error": "conflict-in-package"} + ]) + + self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF") + node.sendrawtransaction(signed_replaceable_tx["hex"]) + testres_rbf_single = node.testmempoolaccept([signed_replacement_tx["hex"]]) + # This transaction is a valid BIP125 replace-by-fee + assert testres_rbf_single[0]["allowed"] + testres_rbf_package = self.independent_txns_testres_blank + [{ + "txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "allowed": False, "reject-reason": "txn-mempool-conflict" + }] + self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package) + +if __name__ == "__main__": + RPCPackagesTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 86c7b3fbcc..53ddf24e47 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -15,6 +15,8 @@ Test the following RPCs: from collections import OrderedDict from decimal import Decimal from io import BytesIO + +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import CTransaction, ToHex from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -66,7 +68,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info('prepare some coins for multiple *rawtransaction commands') self.nodes[2].generate(1) self.sync_all() - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) self.sync_all() self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0) diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index 60b4d1c744..16b0019866 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test transaction signing using the signrawtransaction* RPCs.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.address import check_script, script_to_p2sh, script_to_p2wsh from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework @@ -155,7 +156,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): def test_fully_signed_tx(self): self.log.info("Test signing a fully signed transaction does nothing") self.nodes[0].walletpassphrase("password", 9999) - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) rawtx = self.nodes[0].createrawtransaction([], [{self.nodes[0].getnewaddress(): 10}]) fundedtx = self.nodes[0].fundrawtransaction(rawtx) signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx["hex"]) @@ -174,7 +175,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): embedded_pubkey = eckey.get_pubkey().get_bytes().hex() p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit") # send transaction to P2SH-P2WSH 1-of-1 multisig address - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999) self.nodes[0].generate(1) self.sync_all() diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py index 528da0cbfc..bf96b6353c 100755 --- a/test/functional/rpc_txoutproof.py +++ b/test/functional/rpc_txoutproof.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test gettxoutproof and verifytxoutproof RPCs.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import CMerkleBlock, FromHex, ToHex from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error @@ -23,7 +24,7 @@ class MerkleBlockTest(BitcoinTestFramework): miniwallet = MiniWallet(self.nodes[0]) # Add enough mature utxos to the wallet, so that all txs spend confirmed coins miniwallet.generate(5) - self.nodes[0].generate(100) + self.nodes[0].generate(COINBASE_MATURITY) self.sync_all() chain_height = self.nodes[1].getblockcount() diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index d08e025178..f35ea6c122 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -52,6 +52,9 @@ MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR # Genesis block time (regtest) TIME_GENESIS_BLOCK = 1296688602 +# Coinbase transaction outputs can only be spent after this number of new blocks (network rule) +COINBASE_MATURITY = 100 + # From BIP141 WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed" @@ -178,6 +181,11 @@ def create_raw_transaction(node, txid, to_address, *, amount): signed_psbt = wrpc.walletprocesspsbt(psbt) psbt = signed_psbt['psbt'] final_psbt = node.finalizepsbt(psbt) + if not final_psbt["complete"]: + node.log.info(f'final_psbt={final_psbt}') + for w in node.listwallets(): + wrpc = node.get_wallet_rpc(w) + node.log.info(f'listunspent={wrpc.listunspent()}') assert_equal(final_psbt["complete"], True) return final_psbt['hex'] diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py index e047e7fa14..5dc723c1d5 100644 --- a/test/functional/test_framework/netutil.py +++ b/test/functional/test_framework/netutil.py @@ -151,7 +151,7 @@ def test_ipv6_local(): have_ipv6 = True try: s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - s.connect(('::1', 0)) + s.connect(('::1', 1)) except socket.error: have_ipv6 = False return have_ipv6 diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index c17c16f797..ba52abc7dd 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -400,14 +400,14 @@ class TestNode(): self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager - def profile_with_perf(self, profile_name): + def profile_with_perf(self, profile_name: str): """ Context manager that allows easy profiling of node activity using `perf`. See `test/functional/README.md` for details on perf usage. Args: - profile_name (str): This string will be appended to the + profile_name: This string will be appended to the profile data filename generated by perf. """ subp = self._start_perf(profile_name) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 55166ba0ad..462019566c 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -20,6 +20,7 @@ import unittest from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException from io import BytesIO +from typing import Callable, Optional logger = logging.getLogger("TestFramework.utils") @@ -80,7 +81,7 @@ def assert_raises_message(exc, message, fun, *args, **kwds): raise AssertionError("No exception raised") -def assert_raises_process_error(returncode, output, fun, *args, **kwds): +def assert_raises_process_error(returncode: int, output: str, fun: Callable, *args, **kwds): """Execute a process and asserts the process return code and output. Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError @@ -88,9 +89,9 @@ def assert_raises_process_error(returncode, output, fun, *args, **kwds): no CalledProcessError was raised or if the return code and output are not as expected. Args: - returncode (int): the process return code. - output (string): [a substring of] the process output. - fun (function): the function to call. This should execute a process. + returncode: the process return code. + output: [a substring of] the process output. + fun: the function to call. This should execute a process. args*: positional arguments for the function. kwds**: named arguments for the function. """ @@ -105,7 +106,7 @@ def assert_raises_process_error(returncode, output, fun, *args, **kwds): raise AssertionError("No exception raised") -def assert_raises_rpc_error(code, message, fun, *args, **kwds): +def assert_raises_rpc_error(code: Optional[int], message: Optional[str], fun: Callable, *args, **kwds): """Run an RPC and verify that a specific JSONRPC exception code and message is raised. Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException @@ -113,11 +114,11 @@ def assert_raises_rpc_error(code, message, fun, *args, **kwds): no JSONRPCException was raised or if the error code/message are not as expected. Args: - code (int), optional: the error code returned by the RPC call (defined - in src/rpc/protocol.h). Set to None if checking the error code is not required. - message (string), optional: [a substring of] the error string returned by the - RPC call. Set to None if checking the error string is not required. - fun (function): the function to call. This should be the name of an RPC. + code: the error code returned by the RPC call (defined in src/rpc/protocol.h). + Set to None if checking the error code is not required. + message: [a substring of] the error string returned by the RPC call. + Set to None if checking the error string is not required. + fun: the function to call. This should be the name of an RPC. args*: positional arguments for the function. kwds**: named arguments for the function. """ diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 05bda5d899..0962a5cb54 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -6,6 +6,7 @@ from decimal import Decimal from enum import Enum +from typing import Optional from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE from test_framework.key import ECKey from test_framework.messages import ( @@ -105,12 +106,12 @@ class MiniWallet: def get_address(self): return self._address - def get_utxo(self, *, txid='', mark_as_spent=True): + def get_utxo(self, *, txid: Optional[str]='', mark_as_spent=True): """ Returns a utxo and marks it as spent (pops it from the internal list) Args: - txid (string), optional: get the first utxo we find from a specific transaction + txid: get the first utxo we find from a specific transaction Note: Can be used to get the change output immediately after a send_self_transfer """ @@ -123,13 +124,13 @@ class MiniWallet: else: return self._utxos[index] - def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None): + def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, locktime=0): """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend) self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex']) return tx - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True): + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" self._utxos = sorted(self._utxos, key=lambda k: k['value']) utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee @@ -141,6 +142,7 @@ class MiniWallet: tx = CTransaction() tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']))] tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)] + tx.nLockTime = locktime if not self._address: # raw script if self._priv_key is not None: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 00527e78f1..c9a8cc5611 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -211,6 +211,7 @@ BASE_SCRIPTS = [ 'mempool_package_onemore.py', 'rpc_createmultisig.py --legacy-wallet', 'rpc_createmultisig.py --descriptors', + 'rpc_packages.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py --legacy-wallet', @@ -260,6 +261,7 @@ BASE_SCRIPTS = [ 'wallet_send.py --legacy-wallet', 'wallet_send.py --descriptors', 'wallet_create_tx.py --descriptors', + 'wallet_taproot.py', 'p2p_fingerprint.py', 'feature_uacomment.py', 'wallet_coinbase_category.py --legacy-wallet', @@ -282,6 +284,7 @@ BASE_SCRIPTS = [ 'feature_logging.py', 'feature_anchors.py', 'feature_coinstatsindex.py', + 'wallet_orphanedreward.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', 'feature_blocksdir.py', diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 2e0edcfa38..d24cc802a4 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -12,6 +12,7 @@ """ from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -28,7 +29,7 @@ class AbandonConflictTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.sync_blocks() balance = self.nodes[0].getbalance() txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index b3bee1876d..6d93cf412f 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -53,6 +53,7 @@ Test that the nodes generate the correct change address type: from decimal import Decimal import itertools +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import ( descsum_create, @@ -220,7 +221,7 @@ class AddressTypeTest(BitcoinTestFramework): def run_test(self): # Mine 101 blocks on node5 to bring nodes out of IBD and make sure that # no coinbases are maturing for the nodes-under-test during the test - self.nodes[5].generate(101) + self.nodes[5].generate(COINBASE_MATURITY + 1) self.sync_blocks() uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee" @@ -258,7 +259,7 @@ class AddressTypeTest(BitcoinTestFramework): self.log.info("Sending from node {} ({}) with{} multisig using {}".format(from_node, self.extra_args[from_node], "" if multisig else "out", "default" if address_type is None else address_type)) old_balances = self.get_balances() self.log.debug("Old balances are {}".format(old_balances)) - to_send = (old_balances[from_node] / 101).quantize(Decimal("0.00000001")) + to_send = (old_balances[from_node] / (COINBASE_MATURITY + 1)).quantize(Decimal("0.00000001")) sends = {} addresses = {} diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index f34c1345e0..05a0ef0ea1 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -35,6 +35,7 @@ import os from random import randint import shutil +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -123,7 +124,7 @@ class WalletBackupTest(BitcoinTestFramework): self.sync_blocks() self.nodes[2].generate(1) self.sync_blocks() - self.nodes[3].generate(100) + self.nodes[3].generate(COINBASE_MATURITY) self.sync_blocks() assert_equal(self.nodes[0].getbalance(), 50) @@ -152,7 +153,7 @@ class WalletBackupTest(BitcoinTestFramework): self.do_one_round() # Generate 101 more blocks, so any fees paid mature - self.nodes[3].generate(101) + self.nodes[3].generate(COINBASE_MATURITY + 1) self.sync_all() balance0 = self.nodes[0].getbalance() diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 433b40faee..204a866c55 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -7,6 +7,7 @@ from decimal import Decimal import struct from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -72,7 +73,7 @@ class WalletTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) - self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) + self.nodes[1].generatetoaddress(COINBASE_MATURITY + 1, ADDRESS_WATCHONLY) self.sync_all() if not self.options.descriptors: diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 4a1d25bbc5..a052ec7477 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -6,6 +6,7 @@ from decimal import Decimal from itertools import product +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, @@ -65,7 +66,7 @@ class WalletTest(BitcoinTestFramework): assert_equal(walletinfo['balance'], 0) self.sync_all(self.nodes[0:3]) - self.nodes[1].generate(101) + self.nodes[1].generate(COINBASE_MATURITY + 1) self.sync_all(self.nodes[0:3]) assert_equal(self.nodes[0].getbalance(), 50) @@ -158,7 +159,7 @@ class WalletTest(BitcoinTestFramework): assert_equal(len(self.nodes[1].listlockunspent()), 0) # Have node1 generate 100 blocks (so node0 can recover the fee) - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.sync_all(self.nodes[0:3]) # node0 should end up with 100 btc in block rewards plus fees, but diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index ff5070c1fa..b21461ee7b 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -16,6 +16,7 @@ make assumptions about execution order. from decimal import Decimal import io +from test_framework.blocktools import COINBASE_MATURITY from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction from test_framework.test_framework import BitcoinTestFramework @@ -265,7 +266,7 @@ def test_small_output_with_feerate_succeeds(self, rbf_node, dest_address): self.log.info('Testing small output with feerate bump succeeds') # Make sure additional inputs exist - rbf_node.generatetoaddress(101, rbf_node.getnewaddress()) + rbf_node.generatetoaddress(COINBASE_MATURITY + 1, rbf_node.getnewaddress()) rbfid = spend_one_input(rbf_node, dest_address) input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] assert_equal(len(input_list), 1) diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index 1e032bdd6c..c6f5d334f8 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test descriptor wallet function.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -83,7 +84,7 @@ class WalletDescriptorTest(BitcoinTestFramework): send_wrpc = self.nodes[0].get_wallet_rpc("desc1") # Generate some coins - send_wrpc.generatetoaddress(101, send_wrpc.getnewaddress()) + send_wrpc.generatetoaddress(COINBASE_MATURITY + 1, send_wrpc.getnewaddress()) # Make transactions self.log.info("Test sending and receiving") diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py index 78eef4b790..b28f3ecebc 100755 --- a/test/functional/wallet_fallbackfee.py +++ b/test/functional/wallet_fallbackfee.py @@ -3,6 +3,8 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet replace-by-fee capabilities in conjunction with the fallbackfee.""" + +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_raises_rpc_error @@ -15,7 +17,7 @@ class WalletRBFTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) # sending a transaction without fee estimations must be possible by default on regtest self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index c0b76d960f..f32acb8e15 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet group functionality.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import CTransaction, FromHex, ToHex from test_framework.util import ( @@ -31,7 +32,7 @@ class WalletGroupTest(BitcoinTestFramework): def run_test(self): self.log.info("Setting up") # Mine some coins - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) # Get some addresses from the two nodes addr1 = [self.nodes[1].getnewaddress() for _ in range(3)] diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 23d132df41..d41a389197 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -7,6 +7,7 @@ import os import shutil +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -48,7 +49,7 @@ class WalletHDTest(BitcoinTestFramework): # Derive some HD addresses and remember the last # Also send funds to each add - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) hd_add = None NUM_HD_ADDS = 10 for i in range(1, NUM_HD_ADDS + 1): diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index 0a3dd56620..a2da16e5a3 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -16,6 +16,7 @@ variants. and test the values returned.""" from test_framework.address import key_to_p2pkh +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import descsum_create from test_framework.util import ( @@ -73,7 +74,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0) self.log.info('Mining coins') - w0.generatetoaddress(101, w0.getnewaddress()) + w0.generatetoaddress(COINBASE_MATURITY + 1, w0.getnewaddress()) # RPC importdescriptors ----------------------------------------------- diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 13186b9e1d..0a00c5eed9 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -15,6 +15,7 @@ variants. - `test_address()` is called to call getaddressinfo for an address on node1 and test the values returned.""" +from test_framework.blocktools import COINBASE_MATURITY from test_framework.script import ( CScript, OP_NOP, @@ -255,7 +256,7 @@ class ImportMultiTest(BitcoinTestFramework): # P2SH address multisig = get_multisig(self.nodes[0]) - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] @@ -276,7 +277,7 @@ class ImportMultiTest(BitcoinTestFramework): # P2SH + Redeem script multisig = get_multisig(self.nodes[0]) - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] @@ -297,7 +298,7 @@ class ImportMultiTest(BitcoinTestFramework): # P2SH + Redeem script + Private Keys + !Watchonly multisig = get_multisig(self.nodes[0]) - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] @@ -323,7 +324,7 @@ class ImportMultiTest(BitcoinTestFramework): # P2SH + Redeem script + Private Keys + Watchonly multisig = get_multisig(self.nodes[0]) - self.nodes[1].generate(100) + self.nodes[1].generate(COINBASE_MATURITY) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 7635ce2139..ded0e64b1d 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -5,6 +5,7 @@ """Test the importprunedfunds and removeprunedfunds RPCs.""" from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.address import key_to_p2wpkh from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework @@ -24,7 +25,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework): def run_test(self): self.log.info("Mining blocks...") - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) self.sync_all() @@ -46,7 +47,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework): self.sync_all() # Node 1 sync test - assert_equal(self.nodes[1].getblockcount(), 101) + assert_equal(self.nodes[1].getblockcount(), COINBASE_MATURITY + 1) # Address Test - before import address_info = self.nodes[1].getaddressinfo(address1) diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index 5619d57947..1ecf08b9ac 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -13,6 +13,7 @@ Two nodes. Node1 is under test. Node0 is providing transactions and generating b import os import shutil +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -31,7 +32,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): def run_test(self): wallet_path = os.path.join(self.nodes[1].datadir, self.chain, "wallets", self.default_wallet_name, self.wallet_data_filename) wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak") - self.nodes[0].generate(101) + self.nodes[0].generate(COINBASE_MATURITY + 1) self.log.info("Make backup of wallet") self.stop_node(1) diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 551eb72720..2d792bac52 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -11,6 +11,7 @@ RPCs tested are: """ from collections import defaultdict +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet_util import test_address @@ -32,7 +33,7 @@ class WalletLabelsTest(BitcoinTestFramework): # Note each time we call generate, all generated coins go into # the same address, so we call twice to get two addresses w/50 each node.generatetoaddress(nblocks=1, address=node.getnewaddress(label='coinbase')) - node.generatetoaddress(nblocks=101, address=node.getnewaddress(label='coinbase')) + node.generatetoaddress(nblocks=COINBASE_MATURITY + 1, address=node.getnewaddress(label='coinbase')) assert_equal(node.getbalance(), 100) # there should be 2 address groups @@ -104,7 +105,7 @@ class WalletLabelsTest(BitcoinTestFramework): label.verify(node) assert_equal(node.getreceivedbylabel(label.name), 2) label.verify(node) - node.generate(101) + node.generate(COINBASE_MATURITY + 1) # Check that setlabel can assign a label to a new unused address. for label in labels: @@ -124,7 +125,7 @@ class WalletLabelsTest(BitcoinTestFramework): label.add_address(multisig_address) label.purpose[multisig_address] = "send" label.verify(node) - node.generate(101) + node.generate(COINBASE_MATURITY + 1) # Check that setlabel can change the label of an address from a # different label. diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 448720530c..3899971bd7 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -5,6 +5,7 @@ """Test the listsinceblock RPC.""" from test_framework.address import key_to_p2wpkh +from test_framework.blocktools import COINBASE_MATURITY from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import BIP125_SEQUENCE_NUMBER @@ -29,7 +30,7 @@ class ListSinceBlockTest(BitcoinTestFramework): # All nodes are in IBD from genesis, so they'll need the miner (node2) to be an outbound connection, or have # only one connection. (See fPreferredDownload in net_processing) self.connect_nodes(1, 2) - self.nodes[2].generate(101) + self.nodes[2].generate(COINBASE_MATURITY + 1) self.sync_all() self.test_no_blockhash() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 71d1b96a95..00d2c9ffe4 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -14,6 +14,7 @@ import stat import time from test_framework.authproxy import JSONRPCException +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.util import ( @@ -229,7 +230,7 @@ class MultiWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) w1, w2, w3, w4, *_ = wallets - node.generatetoaddress(nblocks=101, address=w1.getnewaddress()) + node.generatetoaddress(nblocks=COINBASE_MATURITY + 1, address=w1.getnewaddress()) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) diff --git a/test/functional/wallet_orphanedreward.py b/test/functional/wallet_orphanedreward.py new file mode 100755 index 0000000000..097df2cf41 --- /dev/null +++ b/test/functional/wallet_orphanedreward.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test orphaned block rewards in the wallet.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +class OrphanedBlockRewardTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + # Generate some blocks and obtain some coins on node 0. We send + # some balance to node 1, which will hold it as a single coin. + self.nodes[0].generate(150) + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 10) + self.nodes[0].generate(1) + + # Get a block reward with node 1 and remember the block so we can orphan + # it later. + self.sync_blocks() + blk = self.nodes[1].generate(1)[0] + self.sync_blocks() + + # Let the block reward mature and send coins including both + # the existing balance and the block reward. + self.nodes[0].generate(150) + self.sync_blocks() + assert_equal(self.nodes[1].getbalance(), 10 + 25) + txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 30) + + # Orphan the block reward and make sure that the original coins + # from the wallet can still be spent. + self.nodes[0].invalidateblock(blk) + self.nodes[0].generate(152) + self.sync_blocks() + # Without the following abandontransaction call, the coins are + # not considered available yet. + assert_equal(self.nodes[1].getbalances()["mine"], { + "trusted": 0, + "untrusted_pending": 0, + "immature": 0, + }) + # The following abandontransaction is necessary to make the later + # lines succeed, and probably should not be needed; see + # https://github.com/bitcoin/bitcoin/issues/14148. + self.nodes[1].abandontransaction(txid) + assert_equal(self.nodes[1].getbalances()["mine"], { + "trusted": 10, + "untrusted_pending": 0, + "immature": 0, + }) + self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 9) + +if __name__ == '__main__': + OrphanedBlockRewardTest().main() diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py new file mode 100755 index 0000000000..65ca7bdef7 --- /dev/null +++ b/test/functional/wallet_taproot.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test generation and spending of P2TR addresses.""" + +import random + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +from test_framework.descriptors import descsum_create +from test_framework.script import (CScript, OP_CHECKSIG, taproot_construct) +from test_framework.segwit_addr import encode_segwit_address + +# xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation) +KEYS = [ + { + "xprv": "tprv8ZgxMBicQKsPeNLUGrbv3b7qhUk1LQJZAGMuk9gVuKh9sd4BWGp1eMsehUni6qGb8bjkdwBxCbgNGdh2bYGACK5C5dRTaif9KBKGVnSezxV", + "xpub": "tpubD6NzVbkrYhZ4XqNGAWGWSzmxGWFwVjVTjZxh2fioKbVYi7Jx8fdbprVWsdW7mHwqjchBVas8TLZG4Xwuz4RKU4iaCqiCvoSkFCzQptqk5Y1", + "pubs": [ + "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18", + "a30253b018ea6fca966135bf7dd8026915427f24ccf10d4e03f7870f4128569b", + "a61e5749f2f3db9dc871d7b187e30bfd3297eea2557e9be99897ea8ff7a29a21", + "8110cf482f66dc37125e619d73075af932521724ffc7108309e88f361efe8c8a", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPe98QUPieXy5KFPVjuZNpcC9JY7K7buJEm8nWvJogK4kTda7eLjK9U4PnMNbSjEkpjDJazeBZ4rhYNYD7N6GEdaysj1AYSb5", + "xpub": "tpubD6NzVbkrYhZ4XcACN3PEwNjRpR1g4tZjBVk5pdMR2B6dbd3HYhdGVZNKofAiFZd9okBserZvv58A6tBX4pE64UpXGNTSesfUW7PpW36HuKz", + "pubs": [ + "f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c", + "71522134160685eb779857033bfc84c7626f13556154653a51dd42619064e679", + "48957b4158b2c5c3f4c000f51fd2cf0fd5ff8868ebfb194256f5e9131fc74bd8", + "086dda8139b3a84944010648d2b674b70447be3ae59322c09a4907bc80be62c1", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPe3ZJmcj9aJ2EPZJYYCh6Lp3v82p75wspgaXmtDZ2RBtkAtWcGnW2VQDzMHQPBkCKMoYTqh1RfJKjv4PcmWVR7KqTpjsdboN", + "xpub": "tpubD6NzVbkrYhZ4XWb6fGPjyhgLxapUhXszv7ehQYrQWDgDX4nYWcNcbgWcM2RhYo9s2mbZcfZJ8t5LzYcr24FK79zVybsw5Qj3Rtqug8jpJMy", + "pubs": [ + "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7", + "8a104c54cd34acba60c97dd8f1f7abc89ba9587afd88dc928e91aca7b1c50d20", + "13ba6b252a4eb5ef31d39cb521724cdab19a698323f5c17093f28fb1821d052f", + "f6c2b4863fd5ba1ba09e3a890caed8b75ffbe013ebab31a06ab87cd6f72506af", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPdKziibn63Rm6aNzp7dSjDnufZMStXr71Huz7iihCRpbZZZ6Voy5HyuHCWx6foHMipzMzUq4tZrtkZ24DJwz5EeNWdsuwX5h", + "xpub": "tpubD6NzVbkrYhZ4Wo2WcFSgSqRD9QWkGxddo6WSqsVBx7uQ8QEtM7WncKDRjhFEexK119NigyCsFygA4b7sAPQxqebyFGAZ9XVV1BtcgNzbCRR", + "pubs": [ + "03a669ea926f381582ec4a000b9472ba8a17347f5fb159eddd4a07036a6718eb", + "bbf56b14b119bccafb686adec2e3d2a6b51b1626213590c3afa815d1fd36f85d", + "2994519e31bbc238a07d82f85c9832b831705d2ee4a2dbb477ecec8a3f570fe5", + "68991b5c139a4c479f8c89d6254d288c533aefc0c5b91fac6c89019c4de64988", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPen4PGtDwURYnCtVMDejyE8vVwMGhQWfVqB2FBPdekhTacDW4vmsKTsgC1wsncVqXiZdX2YFGAnKoLXYf42M78fQJFzuDYFN", + "xpub": "tpubD6NzVbkrYhZ4YF6BAXtXsqCtmv1HNyvsoSXHDsJzpnTtffH1onTEwC5SnLzCHPKPebh2i7Gxvi9kJNADcpuSmH8oM3rCYcHVtdXHjpYoKnX", + "pubs": [ + "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247", + "c8558b7caf198e892032d91f1a48ee9bdc25462b83b4d0ac62bb7fb2a0df630e", + "8a4bcaba0e970685858d133a4d0079c8b55bbc755599e212285691eb779ce3dc", + "b0d68ada13e0d954b3921b88160d4453e9c151131c2b7c724e08f538a666ceb3", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPd91vCgRmbzA13wyip2RimYeVEkAyZvsEN5pUSB3T43SEBxPsytkxb42d64W2EiRE9CewpJQkzR8HKHLV8Uhk4dMF5yRPaTv", + "xpub": "tpubD6NzVbkrYhZ4Wc3i6L6N1Pp7cyVeyMcdLrFGXGDGzCfdCa5F4Zs3EY46N72Ws8QDEUYBVwXfDfda2UKSseSdU1fsBegJBhGCZyxkf28bkQ6", + "pubs": [ + "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744", + "8e971b781b7ce7ab742d80278f2dfe7dd330f3efd6d00047f4a2071f2e7553cb", + "b811d66739b9f07435ccda907ec5cd225355321c35e0a7c7791232f24cf10632", + "4cd27a5552c272bc80ba544e9cc6340bb906969f5e7a1510b6cef9592683fbc9", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPdEhLRxxwzTv2t18j7ruoffPeqAwVA2qXJ2P66RaMZLUWQ85SjoA7xPxdSgCB9UZ72m65qbnaLPtFTfHVP3MEmkpZk1Bv8RT", + "xpub": "tpubD6NzVbkrYhZ4Whj8KcdYPsa9T2efHC6iExzS7gynaJdv8WdripPwjq6NaH5gQJGrLmvUwHY1smhiakUosXNDTEa6qfKUQdLKV6DJBre6XvQ", + "pubs": [ + "d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b", + "cb1d1b1dc62fec1894d4c3d9a1b6738e5ff9c273a64f74e9ab363095f45e9c47", + "245be588f41acfaeb9481aa132717db56ee1e23eb289729fe2b8bde8f9a00830", + "5bc4ad6d6187fa82728c85a073b428483295288f8aef5722e47305b5872f7169", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPcxbqxzcMAwQpiCD8x6qaZEJTxdKxw4w9GuMzDACTD9yhEsHGfqQcfYX4LivosLDDngTykYEp9JnTdcqY7cHqU8PpeFFKyV3", + "xpub": "tpubD6NzVbkrYhZ4WRddreGwaM4wHDj57S2V8XuFF9NGMLjY7PckqZ23PebZR1wGA4w84uX2vZphdZVsnREjij1ibYjEBTaTVQCEZCLs4xUDapx", + "pubs": [ + "065cc1b92bd99e5a3e626e8296a366b2d132688eb43aea19bc14fd8f43bf07fb", + "5b95633a7dda34578b6985e6bfd85d83ec38b7ded892a9b74a3d899c85890562", + "dc86d434b9a34495c8e845b969d51f80d19a8df03b400353ffe8036a0c22eb60", + "06c8ffde238745b29ae8a97ae533e1f3edf214bba6ec58b5e7b9451d1d61ec19", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPe6zLoU8MTTXgsdJVNBErrYGpoGwHf5VGvwUzdNc7NHeCSzkJkniCxBhZWujXjmD4HZmBBrnr3URgJjM6GxRgMmEhLdqNTWG", + "xpub": "tpubD6NzVbkrYhZ4Xa28h7nwrsBoSepRXWRmRqsc5nyb5MHfmRjmFmRhYnG4d9dC7uxixN5AfsEv1Lz3mCAuWvERyvPgKozHUVjfo8EG6foJGy7", + "pubs": [ + "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b", + "939365e0359ff6bc6f6404ee220714c5d4a0d1e36838b9e2081ede217674e2ba", + "4e8767edcf7d3d90258cfbbea01b784f4d2de813c4277b51279cf808bac410a2", + "d42a2c280940bfc6ede971ae72cde2e1df96c6da7dab06a132900c6751ade208", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPeB5o5oCsN2dVxM2mtJiYERQEBRc4JNwC1DFGYaEdNkmh8jJYVPU76YhkFoRoWTdh1p3yQGykG8TfDW34dKgrgSx28gswUyL", + "xpub": "tpubD6NzVbkrYhZ4Xe7aySsTmSHcXNYi3duSoj11TweMiejaqhW3Ay4DZFPZJses4sfpk4b9VHRhn8v4cKTMjugMM3hqXcqSSmRdiW8QvASXjfY", + "pubs": [ + "e360564b2e0e8d06681b6336a29d0750210e8f34afd9afb5e6fd5fe6dba26c81", + "76b4900f00a1dcce463b6d8e02b768518fce4f9ecd6679a13ad78ea1e4815ad3", + "5575556e263c8ed52e99ab02147cc05a738869afe0039911b5a60a780f4e43d2", + "593b00e2c8d4bd6dda0fd9e238888acf427bb4e128887fd5a40e0e9da78cbc01", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPfEH6jHemkGDjZRnAaKFJVGH8pQU638E6SdbX9hxit1tK2sfFPfL6KS7v8FfUKxstbfEpzSymbdfBM9Y5UkrxErF9fJaKLK3", + "xpub": "tpubD6NzVbkrYhZ4YhJtcwKN9fsr8TJ6jeSD4Zsv6vWPTQ2VH7rHn6nK4WWBCzKK7FkdVVwm3iztCU1UmStY4hX6gRbBmp9UzK9C59dQEzeXS12", + "pubs": [ + "7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763", + "c05e44a9e735d1b1bef62e2c0d886e6fb4923b2649b67828290f5cacc51c71b7", + "b33198b20701afe933226c92fd0e3d51d3f266f1113d864dbd026ae3166ef7f2", + "f99643ac3f4072ee4a949301e86963a9ca0ad57f2ef29f6b84fda037d7cac85b", + ] + }, + { + "xprv": "tprv8ZgxMBicQKsPdNWU38dT6aGxtqJR4oYS5kPpLVBcuKiiu7gqTYqMMqhUG6DP7pPahzPQu36sWSmeLCP1C4AwqcR5FX2RyRoZfd4B8pAnSdX", + "xpub": "tpubD6NzVbkrYhZ4WqYFvnJ3Vyw5TrpME8jLf3zbd1DvKbX7jbwc5wewYLKLSFRzZWV6hZj7XhsXAy7fhE5jB25DiWyNM3ztXbsXHRVCrp5BiPY", + "pubs": [ + "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7", + "83df59d0a5c951cdd62b7ab225a62079f48d2a333a86e66c35420d101446e92e", + "2a654bf234d819055312f9ca03fad5836f9163b09cdd24d29678f694842b874a", + "aa0334ab910047387c912a21ec0dab806a47ffa38365060dbc5d47c18c6e66e7", + ] + }, + { + "xprv": "tprv8mGPkMVz5mZuJDnC2NjjAv7E9Zqa5LCgX4zawbZu5nzTtLb5kGhPwycX4H1gtW1f5ZdTKTNtQJ61hk71F2TdcQ93EFDTpUcPBr98QRji615", + "xpub": "tpubDHxRtmYEE9FaBgoyv2QKaKmLibMWEfPb6NbNE7cCW4nripqrNfWz8UEPEPbHCrakwLvwFfsqoaf4pjX4gWStp4nECRf1QwBKPkLqnY8pHbj", + "pubs": [ + "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3", + "b2749b74d51a78f5fe3ebb3a7c0ff266a468cade143dfa265c57e325177edf00", + "6b8747a6bbe4440d7386658476da51f6e49a220508a7ec77fe7bccc3e7baa916", + "4674bf4d9ebbe01bf0aceaca2472f63198655ecf2df810f8d69b38421972318e", + ] + } +] + +CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX" +CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB" + +# Point with no known discrete log. +H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + + +def key(hex_key): + """Construct an x-only pubkey from its hex representation.""" + return bytes.fromhex(hex_key) + +def pk(hex_key): + """Construct a script expression for taproot_construct for pk(hex_key).""" + return (None, CScript([bytes.fromhex(hex_key), OP_CHECKSIG])) + +def compute_taproot_address(pubkey, scripts): + """Compute the address for a taproot output with given inner key and scripts.""" + tap = taproot_construct(pubkey, scripts) + assert tap.scriptPubKey[0] == 0x51 + assert tap.scriptPubKey[1] == 0x20 + return encode_segwit_address("bcrt", 1, tap.scriptPubKey[2:]) + +class WalletTaprootTest(BitcoinTestFramework): + """Test generation and spending of P2TR address outputs.""" + + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + self.extra_args = [['-keypool=100'], ['-keypool=100']] + self.supports_cli = False + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + def setup_network(self): + self.setup_nodes() + + def init_wallet(self, i): + pass + + @staticmethod + def rand_keys(n): + ret = [] + idxes = set() + for _ in range(n): + while True: + i = random.randrange(len(KEYS)) + if not i in idxes: + break + idxes.add(i) + ret.append(KEYS[i]) + return ret + + @staticmethod + def make_desc(pattern, privmap, keys, pub_only = False): + pat = pattern.replace("$H", H_POINT) + for i in range(len(privmap)): + if privmap[i] and not pub_only: + pat = pat.replace("$%i" % (i + 1), keys[i]['xprv']) + else: + pat = pat.replace("$%i" % (i + 1), keys[i]['xpub']) + return descsum_create(pat) + + @staticmethod + def make_addr(treefn, keys, i): + args = [] + for j in range(len(keys)): + args.append(keys[j]['pubs'][i]) + return compute_taproot_address(*treefn(*args)) + + def do_test_addr(self, comment, pattern, privmap, treefn, keys): + self.log.info("Testing %s address derivation" % comment) + desc = self.make_desc(pattern, privmap, keys, False) + desc_pub = self.make_desc(pattern, privmap, keys, True) + assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub) + result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + for i in range(4): + addr_g = self.addr_gen.getnewaddress(address_type='bech32') + if treefn is not None: + addr_r = self.make_addr(treefn, keys, i) + assert_equal(addr_g, addr_r) + + def do_test(self, comment, pattern, privmap, treefn, nkeys): + keys = self.rand_keys(nkeys) + self.do_test_addr(comment, pattern, privmap, treefn, keys) + + def run_test(self): + self.log.info("Creating wallets...") + self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) + self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen") + + self.do_test( + "tr(XPRV)", + "tr($1/*)", + [True], + lambda k1: (key(k1), []), + 1 + ) + self.do_test( + "tr(H,XPRV)", + "tr($H,pk($1/*))", + [True], + lambda k1: (key(H_POINT), [pk(k1)]), + 1 + ) + self.do_test( + "tr(XPRV,{H,{H,XPUB}})", + "tr($1/*,{pk($H),{pk($H),pk($2/*)}})", + [True, False], + lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]), + 2 + ) + self.do_test( + "tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})", + "tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})", + [False, False, True], + lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]), + 3 + ) + +if __name__ == '__main__': + WalletTaprootTest().main() diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index fbc0f995d2..e7dd7592b6 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -17,6 +17,7 @@ import struct from io import BytesIO +from test_framework.blocktools import COINBASE_MATURITY from test_framework.bdb import dump_bdb_kv from test_framework.messages import deser_compact_size, deser_string from test_framework.test_framework import BitcoinTestFramework @@ -118,11 +119,11 @@ class UpgradeWalletTest(BitcoinTestFramework): assert_equal(wallet.getwalletinfo()["walletversion"], previous_version) def run_test(self): - self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress()) + self.nodes[0].generatetoaddress(COINBASE_MATURITY + 1, self.nodes[0].getnewaddress()) self.dumb_sync_blocks() # # Sanity check the test framework: res = self.nodes[0].getblockchaininfo() - assert_equal(res['blocks'], 101) + assert_equal(res['blocks'], COINBASE_MATURITY + 1) node_master = self.nodes[0] v16_3_node = self.nodes[1] v15_2_node = self.nodes[2] @@ -130,7 +131,7 @@ class UpgradeWalletTest(BitcoinTestFramework): # Send coins to old wallets for later conversion checks. v16_3_wallet = v16_3_node.get_wallet_rpc('wallet.dat') v16_3_address = v16_3_wallet.getnewaddress() - node_master.generatetoaddress(101, v16_3_address) + node_master.generatetoaddress(COINBASE_MATURITY + 1, v16_3_address) self.dumb_sync_blocks() v16_3_balance = v16_3_wallet.getbalance() diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py index c345c382d0..6743c4a49b 100755 --- a/test/functional/wallet_watchonly.py +++ b/test/functional/wallet_watchonly.py @@ -5,6 +5,7 @@ """Test createwallet watchonly arguments. """ +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -36,7 +37,7 @@ class CreateWalletWatchonlyTest(BitcoinTestFramework): wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_change)['pubkey']) # generate some btc for testing - node.generatetoaddress(101, a1) + node.generatetoaddress(COINBASE_MATURITY + 1, a1) # send 1 btc to our watch-only address txid = def_wallet.sendtoaddress(wo_addr, 1) |