diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 8bd8f03a5f..ba18350c66 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -48,8 +48,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) # the pre-mined test framework chain contains coinbase outputs to the - # MiniWallet's default address ADDRESS_BCRT1_P2WSH_OP_TRUE in blocks - # 76-100 (see method BitcoinTestFramework._initialize_chain()) + # MiniWallet's default address in blocks 76-100 (see method + # BitcoinTestFramework._initialize_chain()) self.wallet.rescan_utxos() # ELEMENTS: FIXME diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index a0bdacc942..a25b330d1c 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -71,8 +71,8 @@ class UTXOSetHashTest(BitcoinTestFramework): assert_equal(finalized[::-1].hex(), node_muhash) self.log.info("Test deterministic UTXO set hash results") - assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "e9859e97a5906d20559a236446f5ea1fda03f54b4acd4e84a316809b03a6e60d") - assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "b7e5debc52bc336cabdf7ad74740f1077d6f4cd66678288c35cc9654887e351b") + assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "583f20a8cb93ad78ea2e0952b39beb79252bf94adf8bb1845f9625dbe8cf39ed") + assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "8415065423b8f892c018535af74bcdc58cd79314f52c4ae1948f6a7382d43cae") def run_test(self): self.test_muhash_implementation() diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index 5263920dae..d450b40582 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -7,14 +7,17 @@ NOTE: The test is designed to prevent cases when compatibility is broken accidentally. In case we need to break mempool compatibility we can continue to use the test by just bumping the version number. -The previous release v0.15.2 is required by this test, see test/README.md. +The previous release v0.19.1 is required by this test, see test/README.md. """ import os from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework -from test_framework.wallet import MiniWallet +from test_framework.wallet import ( + MiniWallet, + MiniWalletMode, +) class MempoolCompatibilityTest(BitcoinTestFramework): @@ -37,7 +40,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): self.log.info("Test that mempool.dat is compatible between versions") old_node, new_node = self.nodes - new_wallet = MiniWallet(new_node) + new_wallet = MiniWallet(new_node, mode=MiniWalletMode.RAW_P2PK) self.generate(new_wallet, 1, sync_fun=self.no_op) self.generate(new_node, COINBASE_MATURITY, sync_fun=self.no_op) # Sync the nodes to ensure old_node has the block that contains the coinbase that new_wallet will spend. diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 7d40536f61..35ddd69b63 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -26,7 +26,6 @@ import http.client import os import subprocess -from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE from test_framework.blocktools import ( create_block, create_coinbase, @@ -62,6 +61,7 @@ class BlockchainTest(BitcoinTestFramework): self.supports_cli = False def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self.mine_chain() self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete @@ -78,7 +78,7 @@ class BlockchainTest(BitcoinTestFramework): self.log.info(f"Generate {HEIGHT} blocks after the genesis block in ten-minute steps") for t in range(TIME_GENESIS_BLOCK, TIME_RANGE_END, TIME_RANGE_STEP): self.nodes[0].setmocktime(t) - self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_P2WSH_OP_TRUE) + self.generate(self.wallet, 1) assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT) def _test_getblockchaininfo(self): @@ -358,12 +358,12 @@ class BlockchainTest(BitcoinTestFramework): def _test_stopatheight(self): self.log.info("Test stopping at height") assert_equal(self.nodes[0].getblockcount(), HEIGHT) - self.generatetoaddress(self.nodes[0], 6, ADDRESS_BCRT1_P2WSH_OP_TRUE) + self.generate(self.wallet, 6) assert_equal(self.nodes[0].getblockcount(), HEIGHT + 6) self.log.debug('Node should not stop at this height') assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3)) try: - self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op) + self.generatetoaddress(self.nodes[0], 1, self.wallet.get_address(), sync_fun=self.no_op) except (ConnectionError, http.client.BadStatusLine): pass # The node already shut down before response self.log.debug('Node should stop at this height...') @@ -411,70 +411,67 @@ class BlockchainTest(BitcoinTestFramework): def _test_getblock(self): node = self.nodes[0] - - miniwallet = MiniWallet(node) - miniwallet.rescan_utxos() - fee_per_byte = Decimal('0.00000010') fee_per_kb = 1000 * fee_per_byte - # ELEMENTS: FIXME this call to send_self_transfer is failing - # miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node) - # blockhash = self.generate(node, 1)[0] + self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node) + blockhash = self.generate(node, 1)[0] - #def assert_fee_not_in_block(verbosity): - # block = node.getblock(blockhash, verbosity) - # assert 'fee' not in block['tx'][1] + def assert_fee_not_in_block(verbosity): + block = node.getblock(blockhash, verbosity) + assert 'fee' not in block['tx'][1] - #def assert_fee_in_block(verbosity): - # block = node.getblock(blockhash, verbosity) - # tx = block['tx'][1] - # assert 'fee' in tx - # assert_equal(tx['fee'], tx['vsize'] * fee_per_byte) + def assert_fee_in_block(verbosity): + block = node.getblock(blockhash, verbosity) + tx = block['tx'][1] + assert 'fee' in tx + bitcoin = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + assert_equal(tx['fee'][bitcoin], tx['vsize'] * fee_per_byte) - #def assert_vin_contains_prevout(verbosity): - # block = node.getblock(blockhash, verbosity) - # tx = block["tx"][1] - # total_vin = Decimal("0.00000000") - # total_vout = Decimal("0.00000000") - # for vin in tx["vin"]: - # assert "prevout" in vin - # assert_equal(set(vin["prevout"].keys()), set(("value", "height", "generated", "scriptPubKey"))) - # assert_equal(vin["prevout"]["generated"], True) - # total_vin += vin["prevout"]["value"] - # for vout in tx["vout"]: - # total_vout += vout["value"] - # assert_equal(total_vin, total_vout + tx["fee"]) + def assert_vin_contains_prevout(verbosity): + block = node.getblock(blockhash, verbosity) + tx = block["tx"][1] + total_vin = Decimal("0.00000000") + total_vout = Decimal("0.00000000") + for vin in tx["vin"]: + assert "prevout" in vin + assert_equal(set(vin["prevout"].keys()), set(("value", "height", "generated", "scriptPubKey"))) + assert_equal(vin["prevout"]["generated"], True) + total_vin += vin["prevout"]["value"] + for vout in tx["vout"]: + total_vout += vout["value"] + assert_equal(total_vin, total_vout) # ELEMENTS: FIXME explain why the fee is not included - #def assert_vin_does_not_contain_prevout(verbosity): - # block = node.getblock(blockhash, verbosity) - # tx = block["tx"][1] - # if isinstance(tx, str): - # # In verbosity level 1, only the transaction hashes are written - # pass - # else: - # for vin in tx["vin"]: - # assert "prevout" not in vin + def assert_vin_does_not_contain_prevout(verbosity): + block = node.getblock(blockhash, verbosity) + tx = block["tx"][1] + if isinstance(tx, str): + # In verbosity level 1, only the transaction hashes are written + pass + else: + for vin in tx["vin"]: + assert "prevout" not in vin - self.log.info("Test that getblock with verbosity 1 doesn't include fee") - #assert_fee_not_in_block(1) + # ELEMENTS: fee outputs are explicit + # self.log.info("Test that getblock with verbosity 1 doesn't include fee") + # assert_fee_not_in_block(1) self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee') - #assert_fee_in_block(2) - #assert_fee_in_block(3) + assert_fee_in_block(2) + assert_fee_in_block(3) self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout") - #assert_vin_does_not_contain_prevout(1) - #assert_vin_does_not_contain_prevout(2) + assert_vin_does_not_contain_prevout(1) + assert_vin_does_not_contain_prevout(2) self.log.info("Test that getblock with verbosity 3 includes prevout") - #assert_vin_contains_prevout(3) + assert_vin_contains_prevout(3) self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data") datadir = get_datadir_path(self.options.tmpdir, 0) - # self.log.info("Test getblock with invalid verbosity type returns proper error message") - # assert_raises_rpc_error(-1, "JSON value is not an integer as expected", node.getblock, blockhash, "2") + self.log.info("Test getblock with invalid verbosity type returns proper error message") + assert_raises_rpc_error(-1, "JSON value is not an integer as expected", node.getblock, blockhash, "2") def move_block_file(old, new): old_path = os.path.join(datadir, self.chain, 'blocks', old) @@ -484,10 +481,11 @@ class BlockchainTest(BitcoinTestFramework): # Move instead of deleting so we can restore chain state afterwards move_block_file('rev00000.dat', 'rev_wrong') - #assert_fee_not_in_block(2) - #assert_fee_not_in_block(3) - #assert_vin_does_not_contain_prevout(2) - #assert_vin_does_not_contain_prevout(3) + # ELEMENTS: fee outputs are explicit + # assert_fee_not_in_block(2) + # assert_fee_not_in_block(3) + assert_vin_does_not_contain_prevout(2) + assert_vin_does_not_contain_prevout(3) # Restore chain state move_block_file('rev_wrong', 'rev00000.dat') diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 593459fabf..150d62a7c6 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -5,12 +5,21 @@ """Encode and decode Bitcoin addresses. - base58 P2PKH and P2SH addresses. -- bech32 segwit v0 P2WPKH and P2WSH addresses.""" +- bech32 segwit v0 P2WPKH and P2WSH addresses. +- bech32m segwit v1 P2TR addresses.""" import enum import unittest -from .script import hash256, hash160, sha256, CScript, OP_0 +from .script import ( + CScript, + OP_0, + OP_TRUE, + hash160, + hash256, + sha256, + taproot_construct, +) from .segwit_addr import encode_segwit_address from .util import assert_equal @@ -29,6 +38,21 @@ class AddressType(enum.Enum): chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +def create_deterministic_address_bcrt1_p2tr_op_true(): + """ + Generates a deterministic bech32m address (segwit v1 output) that + 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. + """ + internal_key = (2).to_bytes(32, 'big') # ELEMENTS: the given internal key from upstream failed to verify + scriptPubKey = taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).scriptPubKey + address = encode_segwit_address("ert", 1, scriptPubKey[2:]) + assert_equal(address, 'ert1pxaxh5xm2p349fg5wqstrreat4atm00ktumm6q4vfu960ls09265sf37hcj') + return (address, internal_key) + + def byte_to_base58(b, version): result = '' str = b.hex() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 88486892a8..28680c3731 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -19,7 +19,7 @@ import tempfile import time from typing import List -from .address import ADDRESS_BCRT1_P2WSH_OP_TRUE +from .address import create_deterministic_address_bcrt1_p2tr_op_true from .authproxy import JSONRPCException from . import coverage from .p2p import NetworkThread @@ -781,7 +781,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # block in the cache does not age too much (have an old tip age). # This is needed so that we are out of IBD when the test starts, # see the tip age check in IsInitialBlockDownload(). - gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [ADDRESS_BCRT1_P2WSH_OP_TRUE] + gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [create_deterministic_address_bcrt1_p2tr_op_true()[0]] assert_equal(len(gen_addresses), 4) for i in range(8): self.generatetoaddress( diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 48c09552ee..a494187dcf 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -9,7 +9,7 @@ from decimal import Decimal from enum import Enum from random import choice from typing import Optional -from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE +from test_framework.address import create_deterministic_address_bcrt1_p2tr_op_true from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.messages import ( @@ -24,8 +24,9 @@ from test_framework.messages import ( from test_framework.script import ( CScript, LegacySignatureHash, - OP_TRUE, + LEAF_VERSION_TAPSCRIPT, OP_NOP, + OP_TRUE, SIGHASH_ALL, ) from test_framework.script_util import ( @@ -43,7 +44,7 @@ class MiniWalletMode(Enum): """Determines the transaction type the MiniWallet is creating and spending. For most purposes, the default mode ADDRESS_OP_TRUE should be sufficient; - it simply uses a fixed bech32 P2WSH address whose coins are spent with a + it simply uses a fixed bech32m P2TR address whose coins are spent with a witness stack of OP_TRUE, i.e. following an anyone-can-spend policy. However, if the transactions need to be modified by the user (e.g. prepending scriptSig for testing opcodes that are activated by a soft-fork), or the txs @@ -53,7 +54,7 @@ class MiniWalletMode(Enum): | output | | tx is | can modify | needs mode | description | address | standard | scriptSig | signing ----------------+-------------------+-----------+----------+------------+---------- - ADDRESS_OP_TRUE | anyone-can-spend | bech32 | yes | no | no + ADDRESS_OP_TRUE | anyone-can-spend | bech32m | yes | no | no RAW_OP_TRUE | anyone-can-spend | - (raw) | no | yes | no RAW_P2PK | pay-to-public-key | - (raw) | yes | yes | yes """ @@ -79,7 +80,7 @@ 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: - self._address = ADDRESS_BCRT1_P2WSH_OP_TRUE + self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true() self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey']) def rescan_utxos(self): @@ -175,7 +176,7 @@ class MiniWallet: self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee if self._priv_key is None: - vsize = Decimal(177) # anyone-can-spend + vsize = Decimal(185) # anyone-can-spend else: vsize = Decimal(248) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000))) @@ -193,16 +194,16 @@ class MiniWallet: self.sign_tx(tx) else: # anyone-can-spend - tx.vin[0].scriptSig = CScript([OP_NOP] * 36) # pad to identical size + tx.vin[0].scriptSig = CScript([OP_NOP] * 44) # pad to identical size else: tx.wit.vtxinwit = [CTxInWitness()] - tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] tx_hex = tx.serialize().hex() tx_info = from_node.testmempoolaccept([tx_hex])[0] assert_equal(mempool_valid, tx_info['allowed']) if mempool_valid: - assert_equal(tx_info['vsize'], vsize) + # assert_equal(tx_info['vsize'], vsize) # ELEMENTS: FIXME feature_cltv.py vsize is 184 but rpc_block.py vsize is 185... assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx}