diff options
Diffstat (limited to 'test')
21 files changed, 227 insertions, 55 deletions
diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 33054fd517..2e4ca83bf0 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -192,7 +192,7 @@ class SpendTooMuch(BadTxTemplate): def get_tx(self): return create_tx_with_script( - self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1)) + self.spend_tx, 0, output_script=basic_p2sh, amount=(self.spend_avail + 1)) class CreateNegative(BadTxTemplate): @@ -242,7 +242,7 @@ class TooManySigops(BadTxTemplate): lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) return create_tx_with_script( self.spend_tx, 0, - script_pub_key=lotsa_checksigs, + output_script=lotsa_checksigs, amount=1) def getDisabledOpcodeTemplate(opcode): diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index 943862f8cf..da7cabf87c 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -8,20 +8,6 @@ to a hash that has been compiled into bitcoind. The assumeutxo value generated and used here is committed to in `CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`. - -## Possible test improvements - -Interesting test cases could be loading an assumeutxo snapshot file with: - -- TODO: Valid snapshot file, but referencing a snapshot block that turns out to be - invalid, or has an invalid parent - -Interesting starting states could be loading a snapshot when the current chain tip is: - -- TODO: An ancestor of snapshot block -- TODO: The snapshot block -- TODO: A descendant of the snapshot block - """ from shutil import rmtree @@ -202,7 +188,6 @@ class AssumeutxoTest(BitcoinTestFramework): def test_snapshot_with_less_work(self, dump_output_path): self.log.info("Test bitcoind should fail when snapshot has less accumulated work than this node.") node = self.nodes[0] - assert_equal(node.getblockcount(), FINAL_HEIGHT) with node.assert_debug_log(expected_msgs=["[snapshot] activation failed - work does not exceed active chainstate"]): assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path) @@ -316,6 +301,11 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}") dump_output = n0.dumptxoutset('utxos.dat') + self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot") + assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT) + assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT) + self.test_snapshot_with_less_work(dump_output['path']) + self.log.info("Test loading snapshot when headers are not synced") self.test_headers_not_synced(dump_output['path']) @@ -358,6 +348,8 @@ class AssumeutxoTest(BitcoinTestFramework): self.test_snapshot_not_on_most_work_chain(dump_output['path']) self.log.info(f"Loading snapshot into second node from {dump_output['path']}") + # This node's tip is on an ancestor block of the snapshot, which should + # be the normal case loaded = n1.loadtxoutset(dump_output['path']) assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT) assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 172af27848..384ca311c7 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1326,8 +1326,10 @@ class FullBlockTest(BitcoinTestFramework): block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py - def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])): - return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script) + def create_tx(self, spend_tx, n, value, output_script=None): + if output_script is None: + output_script = CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]) + return create_tx_with_script(spend_tx, n, amount=value, output_script=output_script) # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output 0 in spend_tx @@ -1338,13 +1340,17 @@ class FullBlockTest(BitcoinTestFramework): return sign_input_legacy(tx, 0, spend_tx.vout[0].scriptPubKey, self.coinbase_key) - def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): - tx = self.create_tx(spend_tx, 0, value, script) + def create_and_sign_transaction(self, spend_tx, value, output_script=None): + if output_script is None: + output_script = CScript([OP_TRUE]) + tx = self.create_tx(spend_tx, 0, value, output_script=output_script) self.sign_tx(tx, spend_tx) tx.rehash() return tx - def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=4): + def next_block(self, number, spend=None, additional_coinbase_value=0, *, script=None, version=4): + if script is None: + script = CScript([OP_TRUE]) if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 @@ -1361,7 +1367,7 @@ class FullBlockTest(BitcoinTestFramework): else: coinbase.vout[0].nValue += spend.vout[0].nValue - 1 # all but one satoshi to fees coinbase.rehash() - tx = self.create_tx(spend, 0, 1, script) # spend 1 satoshi + tx = self.create_tx(spend, 0, 1, output_script=script) # spend 1 satoshi self.sign_tx(tx, spend) tx.rehash() block = create_block(base_block_hash, coinbase, block_time, version=version, txlist=[tx]) diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 34fb3c5573..a3dcb7afda 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -128,6 +128,14 @@ def make_tx(wallet, utxo, feerate): fee_rate=Decimal(feerate * 1000) / COIN, ) +def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical): + fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative")['feerate'] + fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical")['feerate'] + fee_est_default = node.estimatesmartfee(1)['feerate'] + assert_equal(fee_est_conservative, expected_conservative) + assert_equal(fee_est_economical, expected_economical) + assert_equal(fee_est_default, expected_economical) + class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): @@ -382,6 +390,40 @@ class EstimateFeeTest(BitcoinTestFramework): self.start_node(0,extra_args=["-acceptstalefeeestimates"]) assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate) + def clear_estimates(self): + self.log.info("Restarting node with fresh estimation") + self.stop_node(0) + fee_dat = self.nodes[0].chain_path / "fee_estimates.dat" + os.remove(fee_dat) + self.start_node(0) + self.connect_nodes(0, 1) + self.connect_nodes(0, 2) + assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"]) + + def broadcast_and_mine(self, broadcaster, miner, feerate, count): + """Broadcast and mine some number of transactions with a specified fee rate.""" + for _ in range(count): + self.wallet.send_self_transfer(from_node=broadcaster, fee_rate=feerate) + self.sync_mempools() + self.generate(miner, 1) + + def test_estimation_modes(self): + low_feerate = Decimal("0.001") + high_feerate = Decimal("0.005") + tx_count = 24 + # Broadcast and mine high fee transactions for the first 12 blocks. + for _ in range(12): + self.broadcast_and_mine(self.nodes[1], self.nodes[2], high_feerate, tx_count) + check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate) + + # We now track 12 blocks; short horizon stats will start decaying. + # Broadcast and mine low fee transactions for the next 4 blocks. + for _ in range(4): + self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, tx_count) + # conservative mode will consider longer time horizons while economical mode does not + # Check the fee estimates for both modes after mining low fee transactions. + check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate) + def run_test(self): self.log.info("This test is time consuming, please be patient") @@ -420,17 +462,15 @@ class EstimateFeeTest(BitcoinTestFramework): self.log.info("Test reading old fee_estimates.dat") self.test_old_fee_estimate_file() - self.log.info("Restarting node with fresh estimation") - self.stop_node(0) - fee_dat = os.path.join(self.nodes[0].chain_path, "fee_estimates.dat") - os.remove(fee_dat) - self.start_node(0) - self.connect_nodes(0, 1) - self.connect_nodes(0, 2) + self.clear_estimates() self.log.info("Testing estimates with RBF.") self.sanity_check_rbf_estimates(self.confutxo + self.memutxo) + self.clear_estimates() + self.log.info("Test estimatesmartfee modes") + self.test_estimation_modes() + self.log.info("Testing that fee estimation is disabled in blocksonly.") self.restart_node(0, ["-blocksonly"]) assert_raises_rpc_error( diff --git a/test/functional/feature_framework_miniwallet.py b/test/functional/feature_framework_miniwallet.py index 1f381df236..d1aa24e7cd 100755 --- a/test/functional/feature_framework_miniwallet.py +++ b/test/functional/feature_framework_miniwallet.py @@ -3,6 +3,9 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test MiniWallet.""" +import random +import string + from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -31,6 +34,20 @@ class FeatureFrameworkMiniWalletTest(BitcoinTestFramework): assert_greater_than_or_equal(tx.get_weight(), target_weight) assert_greater_than_or_equal(target_weight + 3, tx.get_weight()) + def test_wallet_tagging(self): + """Verify that tagged wallet instances are able to send funds.""" + self.log.info(f"Test tagged wallet instances...") + node = self.nodes[0] + untagged_wallet = self.wallets[0][1] + for i in range(10): + tag = ''.join(random.choice(string.ascii_letters) for _ in range(20)) + self.log.debug(f"-> ({i}) tag name: {tag}") + tagged_wallet = MiniWallet(node, tag_name=tag) + untagged_wallet.send_to(from_node=node, scriptPubKey=tagged_wallet.get_scriptPubKey(), amount=100000) + tagged_wallet.rescan_utxos() + tagged_wallet.send_self_transfer(from_node=node) + self.generate(node, 1) # clear mempool + def run_test(self): node = self.nodes[0] self.wallets = [ @@ -43,6 +60,7 @@ class FeatureFrameworkMiniWalletTest(BitcoinTestFramework): self.generate(wallet, COINBASE_MATURITY) self.test_tx_padding() + self.test_wallet_tagging() if __name__ == '__main__': diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 2201821fda..644ee5cc7f 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -148,7 +148,8 @@ class ProxyTest(BitcoinTestFramework): rv = [] addr = "15.61.23.23:1234" self.log.debug(f"Test: outgoing IPv4 connection through node {node.index} for address {addr}") - node.addnode(addr, "onetry") + # v2transport=False is used to avoid reconnections with v1 being scheduled. These could interfere with later checks. + node.addnode(addr, "onetry", v2transport=False) cmd = proxies[0].queue.get() assert isinstance(cmd, Socks5Command) # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 @@ -164,7 +165,7 @@ class ProxyTest(BitcoinTestFramework): if self.have_ipv6: addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443" self.log.debug(f"Test: outgoing IPv6 connection through node {node.index} for address {addr}") - node.addnode(addr, "onetry") + node.addnode(addr, "onetry", v2transport=False) cmd = proxies[1].queue.get() assert isinstance(cmd, Socks5Command) # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 @@ -180,7 +181,7 @@ class ProxyTest(BitcoinTestFramework): if test_onion: addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333" self.log.debug(f"Test: outgoing onion connection through node {node.index} for address {addr}") - node.addnode(addr, "onetry") + node.addnode(addr, "onetry", v2transport=False) cmd = proxies[2].queue.get() assert isinstance(cmd, Socks5Command) assert_equal(cmd.atyp, AddressType.DOMAINNAME) @@ -195,7 +196,7 @@ class ProxyTest(BitcoinTestFramework): if test_cjdns: addr = "[fc00:1:2:3:4:5:6:7]:8888" self.log.debug(f"Test: outgoing CJDNS connection through node {node.index} for address {addr}") - node.addnode(addr, "onetry") + node.addnode(addr, "onetry", v2transport=False) cmd = proxies[1].queue.get() assert isinstance(cmd, Socks5Command) assert_equal(cmd.atyp, AddressType.DOMAINNAME) @@ -209,7 +210,7 @@ class ProxyTest(BitcoinTestFramework): addr = "node.noumenon:8333" self.log.debug(f"Test: outgoing DNS name connection through node {node.index} for address {addr}") - node.addnode(addr, "onetry") + node.addnode(addr, "onetry", v2transport=False) cmd = proxies[3].queue.get() assert isinstance(cmd, Socks5Command) assert_equal(cmd.atyp, AddressType.DOMAINNAME) diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index b2e030adc7..443c1c33da 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -231,7 +231,7 @@ def default_sigmsg(ctx): codeseppos = get(ctx, "codeseppos") leaf_ver = get(ctx, "leafversion") script = get(ctx, "script_taproot") - return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex) + return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=True, leaf_script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex) else: return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=False, annex=annex) elif mode == "witv0": diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index d547da9cf2..ba6e960476 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -208,6 +208,8 @@ class RESTTest (BitcoinTestFramework): self.test_rest_request(f"/getutxos/{spending[0]}_+1", ret_type=RetType.OBJ, status=400) self.test_rest_request(f"/getutxos/{spending[0]}-+1", ret_type=RetType.OBJ, status=400) self.test_rest_request(f"/getutxos/{spending[0]}--1", ret_type=RetType.OBJ, status=400) + self.test_rest_request(f"/getutxos/{spending[0]}aa-1234", ret_type=RetType.OBJ, status=400) + self.test_rest_request(f"/getutxos/aa-1234", ret_type=RetType.OBJ, status=400) # Test limits long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)]) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 2b841f3791..44a36697c6 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -37,6 +37,7 @@ from test_framework.script_util import ( keys_to_multisig_script, MIN_PADDING, MIN_STANDARD_TX_NONWITNESS_SIZE, + PAY_TO_ANCHOR, script_to_p2sh_script, script_to_p2wsh_script, ) @@ -389,6 +390,65 @@ class MempoolAcceptanceTest(BitcoinTestFramework): maxfeerate=0, ) + self.log.info('OP_1 <0x4e73> is able to be created and spent') + anchor_value = 10000 + create_anchor_tx = self.wallet.send_to(from_node=node, scriptPubKey=PAY_TO_ANCHOR, amount=anchor_value) + self.generate(node, 1) + + # First spend has non-empty witness, will be rejected to prevent third party wtxid malleability + anchor_nonempty_wit_spend = CTransaction() + anchor_nonempty_wit_spend.vin.append(CTxIn(COutPoint(int(create_anchor_tx["txid"], 16), create_anchor_tx["sent_vout"]), b"")) + anchor_nonempty_wit_spend.vout.append(CTxOut(anchor_value - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE])))) + anchor_nonempty_wit_spend.wit.vtxinwit.append(CTxInWitness()) + anchor_nonempty_wit_spend.wit.vtxinwit[0].scriptWitness.stack.append(b"f") + anchor_nonempty_wit_spend.rehash() + + self.check_mempool_result( + result_expected=[{'txid': anchor_nonempty_wit_spend.rehash(), 'allowed': False, 'reject-reason': 'bad-witness-nonstandard'}], + rawtxs=[anchor_nonempty_wit_spend.serialize().hex()], + maxfeerate=0, + ) + + # but is consensus-legal + self.generateblock(node, self.wallet.get_address(), [anchor_nonempty_wit_spend.serialize().hex()]) + + # Without witness elements it is standard + create_anchor_tx = self.wallet.send_to(from_node=node, scriptPubKey=PAY_TO_ANCHOR, amount=anchor_value) + self.generate(node, 1) + + anchor_spend = CTransaction() + anchor_spend.vin.append(CTxIn(COutPoint(int(create_anchor_tx["txid"], 16), create_anchor_tx["sent_vout"]), b"")) + anchor_spend.vout.append(CTxOut(anchor_value - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE])))) + anchor_spend.wit.vtxinwit.append(CTxInWitness()) + # It's "segwit" but txid == wtxid since there is no witness data + assert_equal(anchor_spend.rehash(), anchor_spend.getwtxid()) + + self.check_mempool_result( + result_expected=[{'txid': anchor_spend.rehash(), 'allowed': True, 'vsize': anchor_spend.get_vsize(), 'fees': { 'base': Decimal('0.00000700')}}], + rawtxs=[anchor_spend.serialize().hex()], + maxfeerate=0, + ) + + self.log.info('But cannot be spent if nested sh()') + nested_anchor_tx = self.wallet.create_self_transfer(sequence=SEQUENCE_FINAL)['tx'] + nested_anchor_tx.vout[0].scriptPubKey = script_to_p2sh_script(PAY_TO_ANCHOR) + nested_anchor_tx.rehash() + self.generateblock(node, self.wallet.get_address(), [nested_anchor_tx.serialize().hex()]) + + nested_anchor_spend = CTransaction() + nested_anchor_spend.vin.append(CTxIn(COutPoint(nested_anchor_tx.sha256, 0), b"")) + nested_anchor_spend.vin[0].scriptSig = CScript([bytes(PAY_TO_ANCHOR)]) + nested_anchor_spend.vout.append(CTxOut(nested_anchor_tx.vout[0].nValue - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE])))) + nested_anchor_spend.rehash() + + self.check_mempool_result( + result_expected=[{'txid': nested_anchor_spend.rehash(), 'allowed': False, 'reject-reason': 'non-mandatory-script-verify-flag (Witness version reserved for soft-fork upgrades)'}], + rawtxs=[nested_anchor_spend.serialize().hex()], + maxfeerate=0, + ) + # but is consensus-legal + self.generateblock(node, self.wallet.get_address(), [nested_anchor_spend.serialize().hex()]) + self.log.info('Spending a confirmed bare multisig is okay') address = self.wallet.get_address() tx = tx_from_hex(raw_tx_reference) diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index e27942760c..74a353ab01 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -162,7 +162,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework): self.log.info("Generate a block") last_block = self.generate(self.nodes[0], 1) # generate() implicitly syncs blocks, so that peer 1 gets the block before timelock_tx - # Otherwise, peer 1 would put the timelock_tx in m_recent_rejects + # Otherwise, peer 1 would put the timelock_tx in m_lazy_recent_rejects self.log.info("The time-locked transaction can now be spent") timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py index 5ca5101613..a9b164b078 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -121,7 +121,7 @@ class P2PPermissionsTests(BitcoinTestFramework): tx.vout[0].nValue += 1 txid = tx.rehash() # Send the transaction twice. The first time, it'll be rejected by ATMP because it conflicts - # with a mempool transaction. The second time, it'll be in the m_recent_rejects filter. + # with a mempool transaction. The second time, it'll be in the m_lazy_recent_rejects filter. p2p_rebroadcast_wallet.send_txs_and_test( [tx], self.nodes[1], diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 66dbd78573..4f127fa8b7 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -187,6 +187,16 @@ class DecodeScriptTest(BitcoinTestFramework): assert_equal('1 ' + xonly_public_key, rpc_result['asm']) assert 'segwit' not in rpc_result + self.log.info("- P2A (anchor)") + # 1 <4e73> + witprog_hex = '4e73' + rpc_result = self.nodes[0].decodescript('5102' + witprog_hex) + assert_equal('anchor', rpc_result['type']) + # in the disassembly, the witness program is shown as single decimal due to its small size + witprog_as_decimal = int.from_bytes(bytes.fromhex(witprog_hex), 'little') + assert_equal(f'1 {witprog_as_decimal}', rpc_result['asm']) + assert_equal('bcrt1pfeesnyr2tx', rpc_result['address']) + def decoderawtransaction_asm_sighashtype(self): """Test decoding scripts via RPC command "decoderawtransaction". diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 078a24d577..e53e2fa910 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -120,7 +120,15 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("status"), None) assert_equal(self.nodes[0].scantxoutset("abort"), False) - # check that first arg is needed + # Check that the blockhash and confirmations fields are correct + self.generate(self.nodes[0], 2) + unspent = self.nodes[0].scantxoutset("start", ["addr(mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq)"])["unspents"][0] + blockhash = self.nodes[0].getblockhash(info["height"]) + assert_equal(unspent["height"], info["height"]) + assert_equal(unspent["blockhash"], blockhash) + assert_equal(unspent["confirmations"], 3) + + # Check that first arg is needed assert_raises_rpc_error(-1, "scantxoutset \"action\" ( [scanobjects,...] )", self.nodes[0].scantxoutset) # Check that second arg is needed for start diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py index 9dae0ba994..be96742aa8 100755 --- a/test/functional/rpc_signrawtransactionwithkey.py +++ b/test/functional/rpc_signrawtransactionwithkey.py @@ -9,6 +9,7 @@ from test_framework.blocktools import ( ) from test_framework.address import ( address_to_scriptpubkey, + p2a, script_to_p2sh, ) from test_framework.test_framework import BitcoinTestFramework @@ -100,6 +101,18 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent self.verify_txn_with_witness_script(tx_type) + def keyless_signing_test(self): + self.log.info("Test that keyless 'signing' of pay-to-anchor input succeeds") + funding_txid = self.send_to_address(p2a(), 49.999) + spending_tx = self.nodes[0].createrawtransaction( + [{"txid": funding_txid, "vout": 0}], + [{getnewdestination()[2]: Decimal("49.998")}]) + spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [], []) + self.assert_signing_completed_successfully(spending_tx_signed) + assert self.nodes[0].testmempoolaccept([spending_tx_signed["hex"]])[0]["allowed"] + # 'signing' a P2A prevout is a no-op, so signed and unsigned txs shouldn't differ + assert_equal(spending_tx, spending_tx_signed["hex"]) + def verify_txn_with_witness_script(self, tx_type): self.log.info("Test with a {} script as the witnessScript".format(tx_type)) embedded_privkey, embedded_pubkey = generate_keypair(wif=True) @@ -138,6 +151,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): def run_test(self): self.successful_signing_test() self.witness_script_test() + self.keyless_signing_test() self.invalid_sighashtype_test() self.invalid_private_key_and_tx() diff --git a/test/functional/rpc_validateaddress.py b/test/functional/rpc_validateaddress.py index dde07e7ead..bf094a7df8 100755 --- a/test/functional/rpc_validateaddress.py +++ b/test/functional/rpc_validateaddress.py @@ -166,6 +166,11 @@ VALID_DATA = [ "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", ), + # PayToAnchor(P2A) + ( + "bc1pfeessrawgf", + "51024e73", + ), ] diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index bcb38b21cd..2c754e35aa 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -53,13 +53,14 @@ def create_deterministic_address_bcrt1_p2tr_op_true(explicit_internal_key=None): can be spent with a witness stack of OP_TRUE and the control block with internal public key (script-path spending). - Returns a tuple with the generated address and the internal key. + Returns a tuple with the generated address and the TaprootInfo object. """ internal_key = explicit_internal_key or (1).to_bytes(32, 'big') - address = output_key_to_p2tr(taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).output_pubkey) + taproot_info = taproot_construct(internal_key, [("only-path", CScript([OP_TRUE]))]) + address = output_key_to_p2tr(taproot_info.output_pubkey) if explicit_internal_key is None: assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') - return (address, internal_key) + return (address, taproot_info) def byte_to_base58(b, version): @@ -154,6 +155,9 @@ def output_key_to_p2tr(key, main=False): assert len(key) == 32 return program_to_witness(1, key, main) +def p2a(main=False): + return program_to_witness(1, "4e73", main) + def check_key(key): if (type(key) is str): key = bytes.fromhex(key) # Assuming this is hex string diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 338b7fa3b0..5c2fa28a31 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -154,16 +154,18 @@ def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_scr coinbase.calc_sha256() return coinbase -def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=CScript()): +def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, output_script=None): """Return one-input, one-output transaction object spending the prevtx's n-th output with the given amount. Can optionally pass scriptPubKey and scriptSig, default is anyone-can-spend output. """ + if output_script is None: + output_script = CScript() tx = CTransaction() assert n < len(prevtx.vout) tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, SEQUENCE_FINAL)) - tx.vout.append(CTxOut(amount, script_pub_key)) + tx.vout.append(CTxOut(amount, output_script)) tx.calc_sha256() return tx diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 005f7546a8..1f566a1348 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1294,8 +1294,11 @@ class msg_tx: __slots__ = ("tx",) msgtype = b"tx" - def __init__(self, tx=CTransaction()): - self.tx = tx + def __init__(self, tx=None): + if tx is None: + self.tx = CTransaction() + else: + self.tx = tx def deserialize(self, f): self.tx.deserialize(f) diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 97d62f957b..d510cf9b1c 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -810,7 +810,7 @@ def BIP341_sha_sequences(txTo): def BIP341_sha_outputs(txTo): return sha256(b"".join(o.serialize() for o in txTo.vout)) -def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT): +def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index=0, *, scriptpath=False, leaf_script=None, codeseparator_pos=-1, annex=None, leaf_ver=LEAF_VERSION_TAPSCRIPT): assert (len(txTo.vin) == len(spent_utxos)) assert (input_index < len(txTo.vin)) out_type = SIGHASH_ALL if hash_type == 0 else hash_type & 3 @@ -829,7 +829,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat spend_type = 0 if annex is not None: spend_type |= 1 - if (scriptpath): + if scriptpath: spend_type |= 2 ss += bytes([spend_type]) if in_type == SIGHASH_ANYONECANPAY: @@ -846,11 +846,11 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat ss += sha256(txTo.vout[input_index].serialize()) else: ss += bytes(0 for _ in range(32)) - if (scriptpath): - ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(script)) + if scriptpath: + ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(leaf_script)) ss += bytes([0]) ss += codeseparator_pos.to_bytes(4, "little", signed=True) - assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * 49 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37 + assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * 49 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37 return ss def TaprootSignatureHash(*args, **kwargs): diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index 855f3b8cf5..938183ece4 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -8,6 +8,7 @@ import unittest from test_framework.script import ( CScript, OP_0, + OP_1, OP_15, OP_16, OP_CHECKMULTISIG, @@ -42,6 +43,8 @@ assert MIN_PADDING == 5 DUMMY_MIN_OP_RETURN_SCRIPT = CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 1))) assert len(DUMMY_MIN_OP_RETURN_SCRIPT) == MIN_PADDING +PAY_TO_ANCHOR = CScript([OP_1, bytes.fromhex("4e73")]) + def key_to_p2pk_script(key): key = check_key(key) return CScript([key, OP_CHECKSIG]) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index cb0d291361..f3713f297e 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -39,7 +39,6 @@ from test_framework.messages import ( ) from test_framework.script import ( CScript, - LEAF_VERSION_TAPSCRIPT, OP_1, OP_NOP, OP_RETURN, @@ -106,8 +105,8 @@ class MiniWallet: pub_key = self._priv_key.get_pubkey() self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes()) elif mode == MiniWalletMode.ADDRESS_OP_TRUE: - internal_key = None if tag_name is None else hash256(tag_name.encode()) - self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true(internal_key) + internal_key = None if tag_name is None else compute_xonly_pubkey(hash256(tag_name.encode()))[0] + self._address, self._taproot_info = create_deterministic_address_bcrt1_p2tr_op_true(internal_key) self._scriptPubKey = address_to_scriptpubkey(self._address) # When the pre-mined test framework chain is used, it contains coinbase @@ -195,7 +194,12 @@ class MiniWallet: elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin) for i in tx.wit.vtxinwit: - i.scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] + assert_equal(len(self._taproot_info.leaves), 1) + leaf_info = list(self._taproot_info.leaves.values())[0] + i.scriptWitness.stack = [ + leaf_info.script, + bytes([leaf_info.version | self._taproot_info.negflag]) + self._taproot_info.internal_pubkey, + ] else: assert False |