From bbb6599e2adf0476565bfc7523ffc6a55146428f Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 29 Sep 2020 23:11:58 +0530 Subject: [PATCH 01/15] merge bitcoin#20039: Convert amounts from float to decimal --- test/functional/mempool_accept.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index d2a807cddbe14..2ed02df9cc9c1 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -92,22 +92,22 @@ def run_test(self): tx = tx_from_hex(raw_tx_0) txid_0 = tx.rehash() self.check_mempool_result( - result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal(str(fee))}}], + result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], rawtxs=[raw_tx_0], ) self.log.info('A final transaction not in the mempool') coin = coins.pop() # Pick a random coin(base) to spend - output_amount = 0.025 + output_amount = Decimal('0.025') raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL outputs=[{node.getnewaddress(): output_amount}], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] - fee_expected = int(coin['amount']) - output_amount + fee_expected = coin['amount'] - output_amount tx = tx_from_hex(raw_tx_final) self.check_mempool_result( - result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal(str(fee_expected))}}], + result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) @@ -191,7 +191,7 @@ def run_test(self): tx = tx_from_hex(raw_tx_reference) # Reference tx should be valid on itself self.check_mempool_result( - result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal(str(0.1 - 0.05))}}], + result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) From 24eb44c6461633607e6e5053907dd5f0fb0a343c Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:37:08 +0000 Subject: [PATCH 02/15] merge bitcoin#22363: use `script_util` helpers for creating P2{PKH,SH} scripts excludes: - 61b6a017a9f99ef072b2d1980dd547eb20093352 (Dash doesn't support SegWit) - 905d672b743edf31531d095ffe800449eaffec69 (see above) --- test/functional/data/invalid_txs.py | 17 ++++++++++---- test/functional/feature_addressindex.py | 23 +++++++++++++------ test/functional/feature_block.py | 9 ++++---- test/functional/feature_fee_estimation.py | 10 ++++---- test/functional/feature_spentindex.py | 16 +++++++++---- test/functional/feature_txindex.py | 13 ++++++++--- test/functional/mempool_accept.py | 7 +++--- test/functional/test_framework/wallet_util.py | 18 ++++++--------- 8 files changed, 70 insertions(+), 43 deletions(-) diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 67839c367e42c..18129be474eb8 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -29,10 +29,17 @@ CTxOut, MAX_MONEY, ) -from test_framework import script as sc from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS - -basic_p2sh = sc.CScript([sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL]) +from test_framework.script import ( + CScript, + OP_0, + OP_CHECKSIG, + OP_TRUE, +) +from test_framework.script_util import ( + script_to_p2sh_script, +) +basic_p2sh = script_to_p2sh_script(CScript([OP_0])) class BadTxTemplate: @@ -95,7 +102,7 @@ class SizeTooSmall(BadTxTemplate): def get_tx(self): tx = CTransaction() tx.vin.append(self.valid_txin) - tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE]))) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx.calc_sha256() return tx @@ -209,7 +216,7 @@ class TooManySigops(BadTxTemplate): expect_disconnect = False def get_tx(self): - lotsa_checksigs = sc.CScript([sc.OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) + lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) return create_tx_with_script( self.spend_tx, 0, script_pub_key=lotsa_checksigs, diff --git a/test/functional/feature_addressindex.py b/test/functional/feature_addressindex.py index dec73931deaa6..a059ff9f7a737 100755 --- a/test/functional/feature_addressindex.py +++ b/test/functional/feature_addressindex.py @@ -7,10 +7,19 @@ # Test addressindex generation and fetching # -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, + COIN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch -from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160 +from test_framework.script_util import ( + keyhash_to_p2pkh_script, + scripthash_to_p2sh_script, +) from test_framework.util import assert_equal class AddressIndexTest(BitcoinTestFramework): @@ -127,7 +136,7 @@ def run_test(self): # Check that outputs with the same address will only return one txid self.log.info("Testing for txid uniqueness...") addressHash = bytes.fromhex("FE30B718DCF0BF8A2A686BF1820C073F8B2C3B37") - scriptPubKey = CScript([OP_HASH160, addressHash, OP_EQUAL]) + scriptPubKey = scripthash_to_p2sh_script(addressHash) unspent = self.nodes[0].listunspent() tx = CTransaction() tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] @@ -153,7 +162,7 @@ def run_test(self): privkey2 = "cU4zhap7nPJAWeMFu4j6jLrfPmqakDAzy8zn8Fhb3oEevdm4e5Lc" address2 = "yeMpGzMj3rhtnz48XsfpB8itPHhHtgxLc3" addressHash2 = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D") - scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + scriptPubKey2 = keyhash_to_p2pkh_script(addressHash2) self.nodes[0].importprivkey(privkey2) unspent = self.nodes[0].listunspent() @@ -245,9 +254,9 @@ def run_test(self): privKey3 = "cRyrMvvqi1dmpiCmjmmATqjAwo6Wu7QTjKu1ABMYW5aFG4VXW99K" address3 = "yWB15aAdpeKuSaQHFVJpBDPbNSLZJSnDLA" addressHash3 = bytes.fromhex("6C186B3A308A77C779A9BB71C3B5A7EC28232A13") - scriptPubKey3 = CScript([OP_DUP, OP_HASH160, addressHash3, OP_EQUALVERIFY, OP_CHECKSIG]) + scriptPubKey3 = keyhash_to_p2pkh_script(addressHash3) # address4 = "2N8oFVB2vThAKury4vnLquW2zVjsYjjAkYQ" - scriptPubKey4 = CScript([OP_HASH160, addressHash3, OP_EQUAL]) + scriptPubKey4 = scripthash_to_p2sh_script(addressHash3) unspent = self.nodes[2].listunspent() tx = CTransaction() @@ -310,7 +319,7 @@ def run_test(self): privkey1 = "cMvZn1pVWntTEcsK36ZteGQXRAcZ8CoTbMXF1QasxBLdnTwyVQCc" address1 = "yM9Eed1bxjy7tYxD3yZDHxjcVT48WdRoB1" address1hash = bytes.fromhex("0909C84A817651502E020AAD0FBCAE5F656E7D8A") - address1script = CScript([OP_DUP, OP_HASH160, address1hash, OP_EQUALVERIFY, OP_CHECKSIG]) + address1script = keyhash_to_p2pkh_script(address1hash) self.nodes[0].sendtoaddress(address1, 10) self.generate(self.nodes[0], 1) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 973f7d0fa8b90..1dc7ce799b2b4 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -35,17 +35,17 @@ OP_CHECKSIGVERIFY, OP_ELSE, OP_ENDIF, - OP_EQUAL, OP_DROP, OP_FALSE, - OP_HASH160, OP_IF, OP_INVALIDOPCODE, OP_RETURN, OP_TRUE, SIGHASH_ALL, SignatureHash, - hash160, +) +from test_framework.script_util import ( + script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -484,8 +484,7 @@ def run_test(self): # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) - redeem_script_hash = hash160(redeem_script) - p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) + p2sh_script = script_to_p2sh_script(redeem_script) # Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE # This must be signed because it is spending a coinbase diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index b50484e88dd54..c5d4273690213 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -18,10 +18,10 @@ OP_1, OP_2, OP_DROP, - OP_EQUAL, - OP_HASH160, OP_TRUE, - hash160, +) +from test_framework.script_util import ( + script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -37,8 +37,8 @@ # time signing. REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP]) REDEEM_SCRIPT_2 = CScript([OP_2, OP_DROP]) -P2SH_1 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_1), OP_EQUAL]) -P2SH_2 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_2), OP_EQUAL]) +P2SH_1 = script_to_p2sh_script(REDEEM_SCRIPT_1) +P2SH_2 = script_to_p2sh_script(REDEEM_SCRIPT_2) # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])] diff --git a/test/functional/feature_spentindex.py b/test/functional/feature_spentindex.py index d11c4450645b6..17f2b12027549 100755 --- a/test/functional/feature_spentindex.py +++ b/test/functional/feature_spentindex.py @@ -9,8 +9,16 @@ from decimal import Decimal -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut -from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160 +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, + COIN, +) +from test_framework.script_util import ( + keyhash_to_p2pkh_script, +) from test_framework.test_node import ErrorMatch from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -62,7 +70,7 @@ def run_test(self): privkey = "cU4zhap7nPJAWeMFu4j6jLrfPmqakDAzy8zn8Fhb3oEevdm4e5Lc" addressHash = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D") - scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + scriptPubKey = keyhash_to_p2pkh_script(addressHash) unspent = self.nodes[0].listunspent() tx = CTransaction() tx_fee = Decimal('0.00001') @@ -100,7 +108,7 @@ def run_test(self): # Check that verbose raw transaction includes address values and input values address2 = "yeMpGzMj3rhtnz48XsfpB8itPHhHtgxLc3" addressHash2 = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D") - scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + scriptPubKey2 = keyhash_to_p2pkh_script(addressHash2) tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(int(txid, 16), 0))] tx2.vout = [CTxOut(amount - int(COIN / 10), scriptPubKey2)] diff --git a/test/functional/feature_txindex.py b/test/functional/feature_txindex.py index d2246cd20eff8..f6366d26f1e1f 100755 --- a/test/functional/feature_txindex.py +++ b/test/functional/feature_txindex.py @@ -7,8 +7,15 @@ # Test txindex generation and fetching # -from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut -from test_framework.script import CScript, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160 +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, +) +from test_framework.script_util import ( + keyhash_to_p2pkh_script, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -46,7 +53,7 @@ def run_test(self): self.log.info("Testing transaction index...") addressHash = bytes.fromhex("C5E4FB9171C22409809A3E8047A29C83886E325D") - scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + scriptPubKey = keyhash_to_p2pkh_script(addressHash) unspent = self.nodes[0].listunspent() tx = CTransaction() tx_fee_sat = 1000 diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 2ed02df9cc9c1..8c17c46ba876f 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -21,16 +21,17 @@ tx_from_hex, ) from test_framework.script import ( - hash160, CScript, OP_0, OP_2, OP_3, OP_CHECKMULTISIG, - OP_EQUAL, OP_HASH160, OP_RETURN, ) +from test_framework.script_util import ( + script_to_p2sh_script, +) from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -306,7 +307,7 @@ def run_test(self): rawtxs=[tx.serialize().hex()], ) tx = tx_from_hex(raw_tx_reference) - output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) + output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=script_to_p2sh_script(b'burn')) num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( diff --git a/test/functional/test_framework/wallet_util.py b/test/functional/test_framework/wallet_util.py index 89cb3a2eeda5a..78d2c2c3d1ac1 100755 --- a/test/functional/test_framework/wallet_util.py +++ b/test/functional/test_framework/wallet_util.py @@ -16,12 +16,10 @@ OP_2, OP_3, OP_CHECKMULTISIG, - OP_CHECKSIG, - OP_DUP, - OP_EQUAL, - OP_EQUALVERIFY, - OP_HASH160, - hash160, +) +from test_framework.script_util import ( + key_to_p2pkh_script, + script_to_p2sh_script, ) Key = namedtuple('Key', ['privkey', @@ -41,10 +39,9 @@ def get_key(node): Returns a named tuple of privkey, pubkey and all address and scripts.""" addr = node.getnewaddress() pubkey = node.getaddressinfo(addr)['pubkey'] - pkh = hash160(bytes.fromhex(pubkey)) return Key(privkey=node.dumpprivkey(addr), pubkey=pubkey, - p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(), + p2pkh_script=key_to_p2pkh_script(pubkey).hex(), p2pkh_addr=key_to_p2pkh(pubkey)) def get_generate_key(): @@ -55,10 +52,9 @@ def get_generate_key(): eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) pubkey = eckey.get_pubkey().get_bytes().hex() - pkh = hash160(bytes.fromhex(pubkey)) return Key(privkey=privkey, pubkey=pubkey, - p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(), + p2pkh_script=key_to_p2pkh_script(pubkey).hex(), p2pkh_addr=key_to_p2pkh(pubkey)) def get_multisig(node): @@ -74,7 +70,7 @@ def get_multisig(node): script_code = CScript([OP_2] + [bytes.fromhex(pubkey) for pubkey in pubkeys] + [OP_3, OP_CHECKMULTISIG]) return Multisig(privkeys=[node.dumpprivkey(addr) for addr in addrs], pubkeys=pubkeys, - p2sh_script=CScript([OP_HASH160, hash160(script_code), OP_EQUAL]).hex(), + p2sh_script=script_to_p2sh_script(script_code).hex(), p2sh_addr=script_to_p2sh(script_code), redeem_script=script_code.hex()) From 473620f230cff0f2cb59064023ec48abe61d4f18 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:39:54 +0000 Subject: [PATCH 03/15] test: make sure `MiniWallet().sendrawtransaction()` returns `txid` Courtesy of f680d271 from bitcoin#22998 --- test/functional/test_framework/wallet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index d0285f3ad9263..26458951a2be6 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -186,8 +186,10 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_ return {'txid': tx_info['txid'], 'hex': tx_hex, 'tx': tx} def sendrawtransaction(self, *, from_node, tx_hex): - from_node.sendrawtransaction(tx_hex) + txid = from_node.sendrawtransaction(tx_hex) self.scan_tx(from_node.decoderawtransaction(tx_hex)) + return txid + def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): """Build a transaction that spends parent_txid.vout[n] and produces one output with From adcc7a187ad8b65a5a0f3f8d791a87aa7f6b9092 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 24 Sep 2021 01:01:52 +0200 Subject: [PATCH 04/15] merge bitcoin#23079: use MiniWallet for p2p_filter.py --- test/functional/p2p_filter.py | 34 ++++++++++++++---------- test/functional/test_framework/wallet.py | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index a040665fbac5b..ab9f58727dc0b 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -8,6 +8,7 @@ from test_framework.messages import ( CInv, + COIN, MAX_BLOOM_FILTER_SIZE, MAX_BLOOM_HASH_FUNCS, MSG_BLOCK, @@ -28,11 +29,15 @@ ) from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet import ( + MiniWallet, + random_p2pkh, +) class P2PBloomFilter(P2PInterface): # This is a P2SH watch-only wallet - watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87' + watch_script_pubkey = bytes.fromhex('a914ffffffffffffffffffffffffffffffffffffffff87') # The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added watch_filter_init = msg_filterload( data= @@ -93,8 +98,9 @@ def set_test_params(self): '-whitelist=noban@127.0.0.1', # immediate tx relay ]] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + def generatetoscriptpubkey(self, scriptpubkey): + """Helper to generate a single block to the given scriptPubKey.""" + return self.generatetodescriptor(self.nodes[0], 1, f'raw({scriptpubkey.hex()})')[0] def test_size_limits(self, filter_peer): self.log.info('Check that too large filter is rejected') @@ -130,8 +136,7 @@ def test_msg_mempool(self): filter_peer = P2PBloomFilter() self.log.debug("Create a tx relevant to the peer before connecting") - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] - txid = self.nodes[0].sendtoaddress(filter_address, 90) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN) self.log.debug("Send a mempool msg after connecting and check that the tx is received") self.nodes[0].add_p2p_connection(filter_peer) @@ -142,8 +147,7 @@ def test_msg_mempool(self): def test_frelay_false(self, filter_peer): self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set") filter_peer.tx_received = False - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] - self.nodes[0].sendtoaddress(filter_address, 90) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN) # Sync to make sure the reason filter_peer doesn't receive the tx is not p2p delays filter_peer.sync_with_ping() assert not filter_peer.tx_received @@ -156,45 +160,44 @@ def test_filter(self, filter_peer): filter_peer.send_and_ping(filter_peer.watch_filter_init) # If fRelay is not already True, sending filterload sets it to True assert self.nodes[0].getpeerinfo()[0]['relaytxes'] - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block') - block_hash = self.generatetoaddress(self.nodes[0], 1, filter_address)[0] + block_hash = self.generatetoscriptpubkey(filter_peer.watch_script_pubkey) txid = self.nodes[0].getblock(block_hash)['tx'][0] filter_peer.wait_for_merkleblock(block_hash) filter_peer.wait_for_tx(txid) self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') filter_peer.tx_received = False - block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0] + block_hash = self.generatetoscriptpubkey(random_p2pkh()) filter_peer.wait_for_merkleblock(block_hash) assert not filter_peer.tx_received self.log.info('Check that we not receive a tx if the filter does not match a mempool tx') filter_peer.merkleblock_received = False filter_peer.tx_received = False - self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2pkh(), amount=7 * COIN) filter_peer.sync_send_with_ping() assert not filter_peer.merkleblock_received assert not filter_peer.tx_received self.log.info('Check that we receive a tx if the filter matches a mempool tx') filter_peer.merkleblock_received = False - txid = self.nodes[0].sendtoaddress(filter_address, 90) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN) filter_peer.wait_for_tx(txid) assert not filter_peer.merkleblock_received self.log.info('Check that after deleting filter all txs get relayed again') filter_peer.send_and_ping(msg_filterclear()) for _ in range(5): - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2pkh(), amount=7 * COIN) filter_peer.wait_for_tx(txid) self.log.info('Check that request for filtered blocks is ignored if no filter is set') filter_peer.merkleblock_received = False filter_peer.tx_received = False with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']): - block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0] + block_hash = self.generatetoscriptpubkey(random_p2pkh()) filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))]) filter_peer.sync_with_ping() assert not filter_peer.merkleblock_received @@ -210,6 +213,9 @@ def test_filter(self, filter_peer): self.nodes[0].disconnect_p2ps() def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + filter_peer = self.nodes[0].add_p2p_connection(P2PBloomFilter()) self.log.info('Test filter size limits') self.test_size_limits(filter_peer) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 26458951a2be6..f795e3f2bf9dc 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -28,6 +28,7 @@ OP_NOP, SIGHASH_ALL, ) +from test_framework.script_util import key_to_p2pkh_script from test_framework.util import ( assert_equal, assert_greater_than_or_equal, @@ -152,6 +153,25 @@ def send_self_transfer(self, **kwargs): self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex']) return tx + def send_to(self, *, from_node, scriptPubKey, amount, fee=1000): + """ + Create and send a tx with an output to a given scriptPubKey/amount, + plus a change output to our internal address. To keep things simple, a + fixed fee given in Satoshi is used. + + Note that this method fails if there is no single internal utxo + available that can cover the cost for the amount and the fixed fee + (the utxo with the largest value is taken). + + Returns a tuple (txid, n) referring to the created external utxo outpoint. + """ + tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx'] + assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee) + tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet + tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned + txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) + return txid, 1 + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" utxo_to_spend = utxo_to_spend or self.get_utxo() @@ -191,6 +211,14 @@ def sendrawtransaction(self, *, from_node, tx_hex): return txid +def random_p2pkh(): + """Generate a random P2PKH scriptPubKey. Can be used when a random destination is needed, + but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC).""" + key = ECKey() + key.generate() + return key_to_p2pkh_script(key.get_pubkey().get_bytes()) + + def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): """Build a transaction that spends parent_txid.vout[n] and produces one output with amount = parent_value with a fee deducted. From f01b2aa5a069069bbe949805dbe8c09de0b1d381 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:48:05 +0200 Subject: [PATCH 05/15] merge bitcoin#23120: Remove unused and confusing main parameter from script_util --- test/functional/test_framework/script_util.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index 40381c6fc1212..53d2acbebdf75 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -3,7 +3,15 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Useful Script constants and utils.""" -from test_framework.script import CScript, hash160, OP_DUP, OP_HASH160, OP_CHECKSIG, OP_EQUAL, OP_EQUALVERIFY +from test_framework.script import ( + CScript, + hash160, + OP_DUP, + OP_HASH160, + OP_CHECKSIG, + OP_EQUAL, + OP_EQUALVERIFY, +) # To prevent a "tx-size-small" policy rule error, a transaction has to have a # size of at least 83 bytes (MIN_STANDARD_TX_SIZE in @@ -25,32 +33,38 @@ DUMMY_P2SH_SCRIPT = CScript([b'a' * 22]) DUMMY_2_P2SH_SCRIPT = CScript([b'b' * 22]) -def keyhash_to_p2pkh_script(hash, main = False): + +def keyhash_to_p2pkh_script(hash): assert len(hash) == 20 return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG]) -def scripthash_to_p2sh_script(hash, main = False): + +def scripthash_to_p2sh_script(hash): assert len(hash) == 20 return CScript([OP_HASH160, hash, OP_EQUAL]) -def key_to_p2pkh_script(key, main = False): + +def key_to_p2pkh_script(key): key = check_key(key) - return keyhash_to_p2pkh_script(hash160(key), main) + return keyhash_to_p2pkh_script(hash160(key)) -def script_to_p2sh_script(script, main = False): + +def script_to_p2sh_script(script): script = check_script(script) - return scripthash_to_p2sh_script(hash160(script), main) + return scripthash_to_p2sh_script(hash160(script)) + def check_key(key): if isinstance(key, str): - key = bytes.fromhex(key) # Assuming this is hex string + key = bytes.fromhex(key) # Assuming this is hex string if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65): return key assert False + def check_script(script): if isinstance(script, str): - script = bytes.fromhex(script) # Assuming this is hex string + script = bytes.fromhex(script) # Assuming this is hex string if isinstance(script, bytes) or isinstance(script, CScript): return script assert False From d908de921f988616e22e9c4ef5648fdf18530809 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 4 Jan 2025 17:29:31 +0000 Subject: [PATCH 06/15] merge bitcoin#23118: introduce `script_util` helper for creating P2PK scripts --- test/functional/feature_asset_locks.py | 10 ++++++---- test/functional/rpc_signrawtransaction.py | 5 ++++- test/functional/test_framework/blocktools.py | 12 ++++++++++-- test/functional/test_framework/script_util.py | 11 ++++++++--- test/functional/test_framework/wallet.py | 8 +++++--- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index 994626d2fafc7..9fc32a1b5d7d0 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -29,10 +29,12 @@ ) from test_framework.script import ( CScript, - OP_CHECKSIG, OP_RETURN, ) -from test_framework.script_util import key_to_p2pkh_script +from test_framework.script_util import ( + key_to_p2pk_script, + key_to_p2pkh_script, +) from test_framework.test_framework import DashTestFramework from test_framework.util import ( assert_equal, @@ -76,7 +78,7 @@ def create_assetlock(self, coin, amount, pubkey): remaining = int(COIN * coin['amount']) - tiny_amount - amount tx_output_ret = CTxOut(amount, CScript([OP_RETURN, b""])) - tx_output = CTxOut(remaining, CScript([pubkey, OP_CHECKSIG])) + tx_output = CTxOut(remaining, key_to_p2pk_script(pubkey)) lock_tx = CTransaction() lock_tx.vin = inputs @@ -93,7 +95,7 @@ def create_assetunlock(self, index, withdrawal, pubkey=None, fee=tiny_amount): node_wallet = self.nodes[0] mninfo = self.mninfo assert_greater_than(int(withdrawal), fee) - tx_output = CTxOut(int(withdrawal) - fee, CScript([pubkey, OP_CHECKSIG])) + tx_output = CTxOut(int(withdrawal) - fee, key_to_p2pk_script(pubkey)) # request ID = sha256("plwdtx", index) request_id_buf = ser_string(b"plwdtx") + struct.pack(">= halvings if (pubkey is not None): - coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG]) + coinbaseoutput.scriptPubKey = key_to_p2pk_script(pubkey) else: coinbaseoutput.scriptPubKey = CScript([OP_TRUE]) coinbase.vout = [coinbaseoutput] diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index 53d2acbebdf75..e22b2e3522c50 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -5,12 +5,12 @@ """Useful Script constants and utils.""" from test_framework.script import ( CScript, - hash160, - OP_DUP, - OP_HASH160, OP_CHECKSIG, + OP_DUP, OP_EQUAL, OP_EQUALVERIFY, + OP_HASH160, + hash160, ) # To prevent a "tx-size-small" policy rule error, a transaction has to have a @@ -34,6 +34,11 @@ DUMMY_2_P2SH_SCRIPT = CScript([b'b' * 22]) +def key_to_p2pk_script(key): + key = check_key(key) + return CScript([key, OP_CHECKSIG]) + + def keyhash_to_p2pkh_script(hash): assert len(hash) == 20 return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG]) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index f795e3f2bf9dc..c13e911d9ee59 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -23,12 +23,14 @@ from test_framework.script import ( CScript, SignatureHash, - OP_CHECKSIG, OP_TRUE, OP_NOP, SIGHASH_ALL, ) -from test_framework.script_util import key_to_p2pkh_script +from test_framework.script_util import ( + key_to_p2pk_script, + key_to_p2pkh_script, +) from test_framework.util import ( assert_equal, assert_greater_than_or_equal, @@ -74,7 +76,7 @@ def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE): self._priv_key = ECKey() self._priv_key.set((1).to_bytes(32, 'big'), True) pub_key = self._priv_key.get_pubkey() - self._scriptPubKey = bytes(CScript([pub_key.get_bytes(), OP_CHECKSIG])) + self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes()) elif mode == MiniWalletMode.ADDRESS_OP_TRUE: self._address = ADDRESS_BCRT1_P2SH_OP_TRUE self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey']) From 75e0be5263c35b82a601a930e7530cddfec85624 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 4 Jan 2025 08:31:50 +0000 Subject: [PATCH 07/15] merge bitcoin#23866: use MiniWallet for rpc_scantxoutset.py --- test/functional/p2p_filter.py | 10 +-- test/functional/rpc_scantxoutset.py | 81 ++++++++++++------------ test/functional/test_framework/wallet.py | 37 +++++++++-- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index ab9f58727dc0b..ae6d419f5ae58 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -31,7 +31,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.wallet import ( MiniWallet, - random_p2pkh, + getnewdestination, ) @@ -169,14 +169,14 @@ def test_filter(self, filter_peer): self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') filter_peer.tx_received = False - block_hash = self.generatetoscriptpubkey(random_p2pkh()) + block_hash = self.generatetoscriptpubkey(getnewdestination()[1]) filter_peer.wait_for_merkleblock(block_hash) assert not filter_peer.tx_received self.log.info('Check that we not receive a tx if the filter does not match a mempool tx') filter_peer.merkleblock_received = False filter_peer.tx_received = False - self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2pkh(), amount=7 * COIN) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN) filter_peer.sync_send_with_ping() assert not filter_peer.merkleblock_received assert not filter_peer.tx_received @@ -190,14 +190,14 @@ def test_filter(self, filter_peer): self.log.info('Check that after deleting filter all txs get relayed again') filter_peer.send_and_ping(msg_filterclear()) for _ in range(5): - txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2pkh(), amount=7 * COIN) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN) filter_peer.wait_for_tx(txid) self.log.info('Check that request for filtered blocks is ignored if no filter is set') filter_peer.merkleblock_received = False filter_peer.tx_received = False with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']): - block_hash = self.generatetoscriptpubkey(random_p2pkh()) + block_hash = self.generatetoscriptpubkey(getnewdestination()[1]) filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))]) filter_peer.sync_with_ping() assert not filter_peer.merkleblock_received diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 43ec5e683284c..14c0999045e50 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -3,11 +3,16 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the scantxoutset rpc call.""" +from test_framework.messages import COIN from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, Decimal +from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.wallet import ( + MiniWallet, + address_to_scriptpubkey, + getnewdestination, +) -import shutil -import os +from decimal import Decimal def descriptors(out): return sorted(u['desc'] for u in out['unspents']) @@ -15,50 +20,43 @@ def descriptors(out): class ScantxoutsetTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.setup_clean_chain = True self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + def sendtodestination(self, destination, amount): + # interpret strings as addresses, assume scriptPubKey otherwise + if isinstance(destination, str): + destination = address_to_scriptpubkey(destination) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=destination, amount=int(COIN * amount)) def run_test(self): - self.log.info("Mining blocks...") - self.generate(self.nodes[0], 110) - - addr1 = self.nodes[0].getnewaddress("") - pubk1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] - addr2 = self.nodes[0].getnewaddress("") - pubk2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] - addr3 = self.nodes[0].getnewaddress("") - pubk3 = self.nodes[0].getaddressinfo(addr3)['pubkey'] - self.nodes[0].sendtoaddress(addr1, 0.001) - self.nodes[0].sendtoaddress(addr2, 0.002) - self.nodes[0].sendtoaddress(addr3, 0.004) + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + + self.log.info("Create UTXOs...") + pubk1, spk1, addr1 = getnewdestination("legacy") + pubk2, spk2, addr2 = getnewdestination("legacy") + pubk3, spk3, addr3 = getnewdestination("legacy") + self.sendtodestination(spk1, 0.001) + self.sendtodestination(spk2, 0.002) + self.sendtodestination(spk3, 0.004) #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK - self.nodes[0].sendtoaddress("yR5yZLjevw5kX3UxGiQN1g96LXGJni2wSS", 0.008) # (m/0'/0'/0') - self.nodes[0].sendtoaddress("yPcxzaQekxTjaJVSaZ58r22o37H8moWPK2", 0.016) # (m/0'/0'/1') - self.nodes[0].sendtoaddress("yhv7iRHSx4SgyvCPmkm6Js8gTuJTtJH9ec", 0.032) # (m/0'/0'/1500') - self.nodes[0].sendtoaddress("yWEdyyKVNbmaiXHkg3LVPqgoXpMA3S6Xt7", 0.064) # (m/0'/0'/0) - self.nodes[0].sendtoaddress("yTGAdq8sSHJ1QrcqSUaMHs8RMj3Bqz3bkb", 0.128) # (m/0'/0'/1) - self.nodes[0].sendtoaddress("yRTNkmjXjhatND4Dv3V4GwBzYJQ4o9ukQr", 0.256) # (m/0'/0'/1500) - self.nodes[0].sendtoaddress("yPwUp9Vwmr4zE6rSuZg3TBxeyRerdRAbNd", 0.512) # (m/1/1/0') - self.nodes[0].sendtoaddress("yLapNU3bG8E8JNGXRhZbRHHDifrqTucGcg", 1.024) # (m/1/1/1') - self.nodes[0].sendtoaddress("yUhbAKf7AcTC5sPXb2dkABKm3FYENqdzv2", 2.048) # (m/1/1/1500') - self.nodes[0].sendtoaddress("yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", 4.096) # (m/1/1/0) - self.nodes[0].sendtoaddress("ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", 8.192) # (m/1/1/1) - self.nodes[0].sendtoaddress("yVCdQxPXJ3SrtTLv8FuLXDNaynz6kmjPNq", 16.384) # (m/1/1/1500) + self.sendtodestination("yR5yZLjevw5kX3UxGiQN1g96LXGJni2wSS", 0.008) # (m/0'/0'/0') + self.sendtodestination("yPcxzaQekxTjaJVSaZ58r22o37H8moWPK2", 0.016) # (m/0'/0'/1') + self.sendtodestination("yhv7iRHSx4SgyvCPmkm6Js8gTuJTtJH9ec", 0.032) # (m/0'/0'/1500') + self.sendtodestination("yWEdyyKVNbmaiXHkg3LVPqgoXpMA3S6Xt7", 0.064) # (m/0'/0'/0) + self.sendtodestination("yTGAdq8sSHJ1QrcqSUaMHs8RMj3Bqz3bkb", 0.128) # (m/0'/0'/1) + self.sendtodestination("yRTNkmjXjhatND4Dv3V4GwBzYJQ4o9ukQr", 0.256) # (m/0'/0'/1500) + self.sendtodestination("yPwUp9Vwmr4zE6rSuZg3TBxeyRerdRAbNd", 0.512) # (m/1/1/0') + self.sendtodestination("yLapNU3bG8E8JNGXRhZbRHHDifrqTucGcg", 1.024) # (m/1/1/1') + self.sendtodestination("yUhbAKf7AcTC5sPXb2dkABKm3FYENqdzv2", 2.048) # (m/1/1/1500') + self.sendtodestination("yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", 4.096) # (m/1/1/0) + self.sendtodestination("ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", 8.192) # (m/1/1/1) + self.sendtodestination("yVCdQxPXJ3SrtTLv8FuLXDNaynz6kmjPNq", 16.384) # (m/1/1/1500) self.generate(self.nodes[0], 1) - self.log.info("Stop node, remove wallet, mine again some blocks...") - self.stop_node(0) - shutil.rmtree(os.path.join(self.nodes[0].datadir, self.chain, 'wallets')) - self.start_node(0, ['-nowallet']) - self.import_deterministic_coinbase_privkeys() - self.generate(self.nodes[0], 110) - scan = self.nodes[0].scantxoutset("start", []) info = self.nodes[0].gettxoutsetinfo() assert_equal(scan['success'], True) @@ -66,13 +64,12 @@ def run_test(self): assert_equal(scan['txouts'], info['txouts']) assert_equal(scan['bestblock'], info['bestblock']) - self.restart_node(0, ['-nowallet']) self.log.info("Test if we have found the non HD unspent outputs.") - assert_equal(self.nodes[0].scantxoutset("start", [ "pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(" + pubk1 + ")", "combo(" + pubk2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) - assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", [ "pkh(" + pubk1.hex() + ")", "pkh(" + pubk2.hex() + ")", "pkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", [ "combo(" + pubk1.hex() + ")", "combo(" + pubk2.hex() + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "addr(" + addr3 + ")"])['total_amount'], Decimal("0.007")) - assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "pkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) self.log.info("Test range validation.") assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": -1}]) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index c13e911d9ee59..6b5d2dde28d9b 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -7,7 +7,11 @@ from copy import deepcopy from decimal import Decimal from enum import Enum -from test_framework.address import ADDRESS_BCRT1_P2SH_OP_TRUE +from test_framework.address import ( + base58_to_byte, + key_to_p2pkh, + ADDRESS_BCRT1_P2SH_OP_TRUE, +) from test_framework.descriptors import descsum_create from test_framework.key import ECKey from random import choice @@ -30,6 +34,8 @@ from test_framework.script_util import ( key_to_p2pk_script, key_to_p2pkh_script, + keyhash_to_p2pkh_script, + scripthash_to_p2sh_script, ) from test_framework.util import ( assert_equal, @@ -213,12 +219,33 @@ def sendrawtransaction(self, *, from_node, tx_hex): return txid -def random_p2pkh(): - """Generate a random P2PKH scriptPubKey. Can be used when a random destination is needed, - but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC).""" +def getnewdestination(address_type='legacy'): + """Generate a random destination of the specified type and return the + corresponding public key, scriptPubKey and address. Supported types are + 'legacy'. Can be used when a random destination is needed, but no + compiled wallet is available (e.g. as replacement to the + getnewaddress/getaddressinfo RPCs).""" key = ECKey() key.generate() - return key_to_p2pkh_script(key.get_pubkey().get_bytes()) + pubkey = key.get_pubkey().get_bytes() + if address_type == 'legacy': + scriptpubkey = key_to_p2pkh_script(pubkey) + address = key_to_p2pkh(pubkey) + else: + assert False + return pubkey, scriptpubkey, address + + +def address_to_scriptpubkey(address): + """Converts a given address to the corresponding output script (scriptPubKey).""" + payload, version = base58_to_byte(address) + if version == 140: # testnet pubkey hash + return keyhash_to_p2pkh_script(payload) + elif version == 19: # testnet script hash + return scripthash_to_p2sh_script(payload) + # TODO: also support other address formats + else: + assert False def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): From 7c313d0956b9b49ea86fbf8ef1314660d96946b7 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 19 Nov 2021 11:05:23 -0800 Subject: [PATCH 08/15] merge bitcoin#23558: run rpc-generateblock.py even with wallet disabled --- test/functional/rpc_generateblock.py | 34 ++++++++++++---------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index 6d880d430af2b..a09be552f9acf 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -6,6 +6,7 @@ ''' from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet import MiniWallet from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -15,14 +16,13 @@ class GenerateBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): node = self.nodes[0] + miniwallet = MiniWallet(node) + miniwallet.rescan_utxos() self.log.info('Generate an empty block to address') - address = node.getnewaddress() + address = miniwallet.get_address() hash = self.generateblock(node, address, [])['hash'] block = node.getblock(hash, 2) assert_equal(len(block['tx']), 1) @@ -42,37 +42,31 @@ def run_test(self): assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) - # Generate 110 blocks to spend - self.generatetoaddress(node, 110, address) - # Generate some extra mempool transactions to verify they don't get mined for _ in range(10): - node.sendtoaddress(address, 0.001) + miniwallet.send_self_transfer(from_node=node) self.log.info('Generate block with txid') - txid = node.sendtoaddress(address, 1) + txid = miniwallet.send_self_transfer(from_node=node)['txid'] hash = self.generateblock(node, address, [txid])['hash'] block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) assert_equal(block['tx'][1], txid) self.log.info('Generate block with raw tx') - utxos = node.listunspent(addresses=[address]) - raw = node.createrawtransaction([{'txid':utxos[0]['txid'], 'vout':utxos[0]['vout']}],[{address:1}]) - signed_raw = node.signrawtransactionwithwallet(raw)['hex'] - hash = self.generateblock(node, address, [signed_raw])['hash'] + rawtx = miniwallet.create_self_transfer(from_node=node)['hex'] + hash = self.generateblock(node, address, [rawtx])['hash'] + block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) txid = block['tx'][1] - assert_equal(node.gettransaction(txid)['hex'], signed_raw) + assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx) self.log.info('Fail to generate block with out of order txs') - raw1 = node.createrawtransaction([{'txid':txid, 'vout':0}],[{address:0.9999}]) - signed_raw1 = node.signrawtransactionwithwallet(raw1)['hex'] - txid1 = node.sendrawtransaction(signed_raw1) - raw2 = node.createrawtransaction([{'txid':txid1, 'vout':0}],[{address:0.999}]) - signed_raw2 = node.signrawtransactionwithwallet(raw2)['hex'] - assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [signed_raw2, txid1]) + txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] + utxo1 = miniwallet.get_utxo(txid=txid1) + rawtx2 = miniwallet.create_self_transfer(from_node=node, utxo_to_spend=utxo1)['hex'] + assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) self.log.info('Fail to generate block with txid not in mempool') missing_txid = '0000000000000000000000000000000000000000000000000000000000000000' From 4da044bc740baf6b7893963d38420008eb15ddb5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 11 Jan 2022 02:08:01 +0100 Subject: [PATCH 09/15] merge bitcoin#24035: use MiniWallet for mempool_accept.py --- test/functional/data/invalid_txs.py | 7 +- test/functional/feature_block.py | 12 ++- test/functional/feature_cltv.py | 9 ++- test/functional/feature_csv_activation.py | 2 +- test/functional/feature_dersig.py | 2 +- test/functional/feature_utxo_set_hash.py | 4 +- test/functional/mempool_accept.py | 81 +++++++++----------- test/functional/mempool_reorg.py | 11 ++- test/functional/mempool_spend_coinbase.py | 2 +- test/functional/p2p_blocksonly.py | 2 +- test/functional/rpc_generateblock.py | 4 +- test/functional/test_framework/blocktools.py | 5 +- test/functional/test_framework/messages.py | 3 +- test/functional/test_framework/wallet.py | 7 +- 14 files changed, 75 insertions(+), 76 deletions(-) diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 18129be474eb8..2750aa959a9af 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -28,6 +28,7 @@ CTxIn, CTxOut, MAX_MONEY, + SEQUENCE_FINAL, ) from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS from test_framework.script import ( @@ -62,7 +63,7 @@ class BadTxTemplate: def __init__(self, *, spend_tx=None, spend_block=None): self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) - self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xffffffff) + self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", SEQUENCE_FINAL) @abc.abstractmethod def get_tx(self, *args, **kwargs): @@ -118,7 +119,7 @@ def get_tx(self): bad_idx = num_indices + 100 tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", SEQUENCE_FINAL)) tx.vout.append(CTxOut(0, basic_p2sh)) tx.calc_sha256() return tx @@ -156,7 +157,7 @@ class NonexistentInput(BadTxTemplate): def get_tx(self): tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", SEQUENCE_FINAL)) tx.vin.append(self.valid_txin) tx.vout.append(CTxOut(1, basic_p2sh)) tx.calc_sha256() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 1dc7ce799b2b4..fd0cca4e56e7e 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -21,6 +21,7 @@ CTransaction, CTxIn, CTxOut, + SEQUENCE_FINAL, uint256_from_compact, uint256_from_str, ) @@ -48,7 +49,10 @@ script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import ( + assert_equal, + assert_greater_than, +) from data import invalid_txs # This functional test assumes DIP0001 is disabled @@ -812,7 +816,7 @@ def run_test(self): b58 = self.next_block(58, spend=out[17]) tx = CTransaction() assert len(out[17].vout) < 42 - tx.vin.append(CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), SEQUENCE_FINAL)) tx.vout.append(CTxOut(0, b"")) tx.calc_sha256() b58 = self.update_block(58, [tx]) @@ -887,7 +891,7 @@ def run_test(self): tx.nLockTime = 0xffffffff # this locktime is non-final tx.vin.append(CTxIn(COutPoint(out[18].sha256, 0))) # don't set nSequence tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) - assert tx.vin[0].nSequence < 0xffffffff + assert_greater_than(SEQUENCE_FINAL, tx.vin[0].nSequence) tx.calc_sha256() b62 = self.update_block(62, [tx]) self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) @@ -1035,7 +1039,7 @@ def run_test(self): bogus_tx = CTransaction() bogus_tx.sha256 = uint256_from_str(b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c") tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", SEQUENCE_FINAL)) tx.vout.append(CTxOut(1, b"")) b70 = self.update_block(70, [tx]) self.send_blocks([b70], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 3c2994d4644ff..5c4168d3a9592 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -13,6 +13,7 @@ ) from test_framework.messages import ( CTransaction, + SEQUENCE_FINAL, msg_block, ) from test_framework.p2p import P2PInterface @@ -56,7 +57,7 @@ def cltv_invalidate(tx, failure_reason): # 3) the lock-time type (height vs. timestamp) of the top stack item and the # nLockTime field are not the same # 4) the top stack item is greater than the transaction's nLockTime field - # 5) the nSequence field of the txin is 0xffffffff + # 5) the nSequence field of the txin is 0xffffffff (SEQUENCE_FINAL) assert failure_reason in range(5) scheme = [ # | Script to prepend to scriptSig | nSequence | nLockTime | @@ -65,7 +66,7 @@ def cltv_invalidate(tx, failure_reason): [[OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP], None, None], [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 1296688602], # timestamp of genesis block [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 50], - [[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 50], + [[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], SEQUENCE_FINAL, 50], ][failure_reason] cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) @@ -119,7 +120,7 @@ def run_test(self): # create one invalid tx per CLTV failure reason (5 in total) and collect them invalid_cltv_txs = [] for i in range(5): - spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + spendtx = wallet.create_self_transfer()['tx'] cltv_invalidate(spendtx, i) invalid_cltv_txs.append(spendtx) @@ -154,7 +155,7 @@ def run_test(self): # 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 = wallet.create_self_transfer()['tx'] cltv_invalidate(spendtx, i) expected_cltv_reject_reason = [ diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 68ff895308f33..11ae97c9e848a 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -110,7 +110,7 @@ def setup_network(self): def create_self_transfer_from_utxo(self, input_tx): utxo = self.miniwallet.get_utxo(txid=input_tx.rehash(), mark_as_spent=False) - tx = self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo)['tx'] + tx = self.miniwallet.create_self_transfer(utxo_to_spend=utxo)['tx'] return tx def create_bip112special(self, input, txversion): diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index a0f55ca7d2d13..4c4d959e6903b 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -60,7 +60,7 @@ def set_test_params(self): def create_tx(self, input_txid): utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid, mark_as_spent=False) - return self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['tx'] + return self.miniwallet.create_self_transfer(utxo_to_spend=utxo_to_spend)['tx'] def test_dersig_info(self, *, is_active): assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip66'], diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index 450e55ed348d6..a2b278af7d832 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -67,8 +67,8 @@ def test_muhash_implementation(self): assert_equal(finalized[::-1].hex(), node_muhash) self.log.info("Test deterministic UTXO set hash results") - assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "4eb23b9673b7472e33ebf6e6aefee849fe5bc5e598fd387c2f9222070cc2f711") - assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "dd74d7f1fb047577915d490e82d063e843f7853ae7543c9980a28c67326e1a2c") + assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "91ceba6561281d14db95afb48894fa6f764daa9f2190d05cb397e759634e10bf") + assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "c75ac7fcf057ef30cb5880fc800a99d3908fd0eb8593647512823ada6e359ef5") def run_test(self): self.test_muhash_implementation() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 8c17c46ba876f..13dd8b0795cac 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool acceptance of raw transactions.""" - +from copy import deepcopy from decimal import Decimal import math @@ -18,6 +18,7 @@ CTxOut, MAX_BLOCK_SIZE, MAX_MONEY, + SEQUENCE_FINAL, tx_from_hex, ) from test_framework.script import ( @@ -36,6 +37,7 @@ assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet class MempoolAcceptanceTest(BitcoinTestFramework): @@ -46,9 +48,6 @@ def set_test_params(self): ]] * self.num_nodes self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def check_mempool_result(self, result_expected, *args, **kwargs): """Wrapper to check result of testmempoolaccept on node_0's mempool""" result_test = self.nodes[0].testmempoolaccept(*args, **kwargs) @@ -57,12 +56,13 @@ def check_mempool_result(self, result_expected, *args, **kwargs): def run_test(self): node = self.nodes[0] + self.wallet = MiniWallet(node) + self.wallet.rescan_utxos() self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) - coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) @@ -71,12 +71,12 @@ def run_test(self): assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') - coin = coins.pop() # Pick a random coin(base) to spend - raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], - outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], - ))['hex'] - txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] # Pick a random coin(base) to spend + tx.vout.append(deepcopy(tx.vout[0])) + tx.vout[0].nValue = int(0.3 * COIN) + tx.vout[1].nValue = int(49 * COIN) + raw_tx_in_block = tx.serialize().hex() + txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 self.check_mempool_result( @@ -86,11 +86,10 @@ def run_test(self): self.log.info('A transaction not in the mempool') fee = Decimal('0.000007') - raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later - outputs=[{node.getnewaddress(): Decimal('0.3') - fee}], - ))['hex'] - tx = tx_from_hex(raw_tx_0) + utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=BIP125_SEQUENCE_NUMBER)['tx'] + tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN) + raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], @@ -98,15 +97,15 @@ def run_test(self): ) self.log.info('A final transaction not in the mempool') - coin = coins.pop() # Pick a random coin(base) to spend output_amount = Decimal('0.025') - raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL - outputs=[{node.getnewaddress(): output_amount}], + tx = self.wallet.create_self_transfer( + sequence=SEQUENCE_FINAL, locktime=node.getblockcount() + 2000, # Can be anything - ))['hex'] - fee_expected = coin['amount'] - output_amount + )['tx'] + tx.vout[0].nValue = int(output_amount * COIN) + raw_tx_final = tx.serialize().hex() tx = tx_from_hex(raw_tx_final) + fee_expected = Decimal('500.0') - output_amount self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], rawtxs=[tx.serialize().hex()], @@ -127,8 +126,7 @@ def run_test(self): tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF - raw_tx_0_reject = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] - tx = tx_from_hex(raw_tx_0_reject) + raw_tx_0_reject = tx.serialize().hex() txid_0_reject = tx.rehash() self.check_mempool_result( # No RBF in DASH @@ -142,7 +140,6 @@ def run_test(self): # take original raw_tx_0 tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}], rawtxs=[tx.serialize().hex()], @@ -152,7 +149,6 @@ def run_test(self): self.log.info('A transaction with missing inputs, that never existed') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[tx.serialize().hex()], @@ -161,17 +157,16 @@ def run_test(self): self.log.info('A transaction with missing inputs, that existed once in the past') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend - raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] + raw_tx_1 = tx.serialize().hex() txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them - raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[ - {'txid': txid_0, 'vout': 0}, - {'txid': txid_1, 'vout': 0}, - ], - outputs=[{node.getnewaddress(): 0.1}] - ))['hex'] - txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] + tx.vin.append(deepcopy(tx.vin[0])) + tx.vin[0].prevout = COutPoint(hash=int(txid_0, 16), n=0) + tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0) + tx.vout[0].nValue = int(0.1 * COIN) + raw_tx_spend_both = tx.serialize().hex() + txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again @@ -184,12 +179,11 @@ def run_test(self): rawtxs=[raw_tx_1], ) - self.log.info('Create a signed "reference" tx for later use') - raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': txid_spend_both, 'vout': 0}], - outputs=[{node.getnewaddress(): 0.05}], - ))['hex'] - tx = tx_from_hex(raw_tx_reference) + self.log.info('Create a "reference" tx for later use') + utxo_to_spend = self.wallet.get_utxo(txid=txid_spend_both) + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=SEQUENCE_FINAL)['tx'] + tx.vout[0].nValue = int(0.05 * COIN) + raw_tx_reference = tx.serialize().hex() # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], @@ -200,8 +194,6 @@ def run_test(self): self.log.info('A transaction with no outputs') tx = tx_from_hex(raw_tx_reference) tx.vout = [] - # Skip re-signing the transaction for context independent checks from now on - # tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], @@ -258,7 +250,7 @@ def run_test(self): ) self.log.info('A coinbase transaction') - # Pick the input of the first tx we signed, so it has to be a coinbase tx + # Pick the input of the first tx we created, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx = tx_from_hex(raw_tx_coinbase_spent) self.check_mempool_result( @@ -341,7 +333,6 @@ def run_test(self): self.log.info('A transaction that is locked by BIP68 sequence logic') tx = tx_from_hex(raw_tx_reference) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one - # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}], rawtxs=[tx.serialize().hex()], diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 781b00fea426c..4385dc86b7aa3 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -45,14 +45,13 @@ def run_test(self): 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) + spend_1 = wallet.create_self_transfer(utxo_to_spend=utxo_1) + spend_2 = wallet.create_self_transfer(utxo_to_spend=utxo_2) + spend_3 = wallet.create_self_transfer(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 @@ -71,9 +70,9 @@ def run_test(self): 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_2_1 = wallet.create_self_transfer(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) + spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo) self.log.info("Broadcast and mine spend_3_1") spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py index e97595ed867ab..80c10ffac18f5 100755 --- a/test/functional/mempool_spend_coinbase.py +++ b/test/functional/mempool_spend_coinbase.py @@ -40,7 +40,7 @@ def run_test(self): spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"] # other coinbase should be too immature to spend - immature_tx = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_immature, mempool_valid=False) + immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature, mempool_valid=False) assert_raises_rpc_error(-26, "bad-txns-premature-spend-of-coinbase", lambda: self.nodes[0].sendrawtransaction(immature_tx['hex'])) diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index eaa83aaf75b79..a79eadc0b5631 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -115,7 +115,7 @@ def check_p2p_inv_violation(self, peer): def check_p2p_tx_violation(self): self.log.info('Check that txs from P2P are rejected and result in disconnect') - spendtx = self.miniwallet.create_self_transfer(from_node=self.nodes[0]) + spendtx = self.miniwallet.create_self_transfer() with self.nodes[0].assert_debug_log(['tx sent in violation of protocol peer=0']): self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx'])) diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index a09be552f9acf..9fce143990e1e 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -54,7 +54,7 @@ def run_test(self): assert_equal(block['tx'][1], txid) self.log.info('Generate block with raw tx') - rawtx = miniwallet.create_self_transfer(from_node=node)['hex'] + rawtx = miniwallet.create_self_transfer()['hex'] hash = self.generateblock(node, address, [rawtx])['hash'] block = node.getblock(hash, 1) @@ -65,7 +65,7 @@ def run_test(self): self.log.info('Fail to generate block with out of order txs') txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] utxo1 = miniwallet.get_utxo(txid=txid1) - rawtx2 = miniwallet.create_self_transfer(from_node=node, utxo_to_spend=utxo1)['hex'] + rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex'] assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) self.log.info('Fail to generate block with txid not in mempool') diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index ca3c2625eb806..e6c225cbbc43d 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -18,6 +18,7 @@ CTransaction, CTxIn, CTxOut, + SEQUENCE_FINAL, tx_from_hex, from_hex, uint256_to_string, @@ -180,7 +181,7 @@ def create_coinbase(height, pubkey=None, dip4_activated=False, v20_activated=Fal If pubkey is passed in, the coinbase output will be a P2PK output; otherwise an anyone-can-spend output.""" coinbase = CTransaction() - coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) + coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), SEQUENCE_FINAL)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = nValue * COIN if nValue == 500: @@ -208,7 +209,7 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C """ tx = CTransaction() assert n < len(prevtx.vout) - tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, SEQUENCE_FINAL)) tx.vout.append(CTxOut(amount, script_pub_key)) tx.calc_sha256() return tx diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 8173df64e8582..95023f840c5b0 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -41,6 +41,7 @@ MAX_MONEY = 21000000 * COIN BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out +SEQUENCE_FINAL = 0xffffffff # Sequence number that disables nLockTime if set for every input of a tx MAX_PROTOCOL_MESSAGE_LENGTH = 3 * 1024 * 1024 # Maximum length of incoming protocol messages MAX_HEADERS_UNCOMPRESSED_RESULT = 2000 # Number of headers sent in one getheaders result @@ -509,7 +510,7 @@ class CTransaction: def __init__(self, tx=None): if tx is None: - self.nVersion = 1 + self.nVersion = 2 self.nType = 0 self.vin = [] self.vout = [] diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 6b5d2dde28d9b..811a44a3228c0 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -180,8 +180,9 @@ def send_to(self, *, from_node, scriptPubKey, amount, fee=1000): txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) return txid, 1 - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" + from_node = from_node or self._test_node utxo_to_spend = utxo_to_spend or self.get_utxo() if self._priv_key is None: vsize = Decimal(85) # anyone-can-spend @@ -213,8 +214,8 @@ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_ assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) return {'txid': tx_info['txid'], 'hex': tx_hex, 'tx': tx} - def sendrawtransaction(self, *, from_node, tx_hex): - txid = from_node.sendrawtransaction(tx_hex) + def sendrawtransaction(self, *, from_node, tx_hex, **kwargs): + txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs) self.scan_tx(from_node.decoderawtransaction(tx_hex)) return txid From 16ffee404ed1d9d80a9a52c59664d76d88ef1964 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:17:42 +0100 Subject: [PATCH 10/15] merge bitcoin#23873: use MiniWallet for p2p_compactblocks.py --- test/functional/p2p_compactblocks.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 96f69e0eea91d..a2cd786c598d5 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -57,6 +57,8 @@ from test_framework.util import ( assert_equal, ) +from test_framework.wallet import MiniWallet + # TestP2PConn: A peer we use to send messages to dashd, and store responses. class TestP2PConn(P2PInterface): @@ -142,9 +144,6 @@ def set_test_params(self): ]] self.utxos = [] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def build_block_on_tip(self, node): block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) block.solve() @@ -155,7 +154,7 @@ def make_utxos(self): block = self.build_block_on_tip(self.nodes[0]) self.test_node.send_and_ping(msg_block(block)) assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 - self.generatetoaddress(self.nodes[0], COINBASE_MATURITY, self.nodes[0].getnewaddress()) + self.generate(self.wallet, COINBASE_MATURITY) total_value = block.vtx[0].vout[0].nValue out_value = total_value // 10 @@ -280,11 +279,9 @@ def test_compactblock_construction(self, test_node): # Generate a bunch of transactions. self.generate(node, COINBASE_MATURITY + 1) num_transactions = 25 - address = node.getnewaddress() for _ in range(num_transactions): - txid = node.sendtoaddress(address, 0.1) - hex_tx = node.gettransaction(txid)["hex"] + hex_tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['hex'] tx = tx_from_hex(hex_tx) # Wait until we've seen the block announcement for the resulting tip @@ -788,8 +785,7 @@ def assert_highbandwidth_states(node, hb_to, hb_from): assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False) def run_test(self): - # Get the nodes out of IBD - self.generate(self.nodes[0], 1) + self.wallet = MiniWallet(self.nodes[0]) # Setup the p2p connections self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) From 0e3dadf795164ae66a673544c4a8fd21d80c00c8 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:49:32 +0000 Subject: [PATCH 11/15] merge bitcoin#23978: use MiniWallet for mining_basic.py --- test/functional/mining_basic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 0e0757ca75a6b..daf634b161256 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -28,6 +28,8 @@ assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet + VERSIONBITS_TOP_BITS = 0x20000000 VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28 @@ -52,7 +54,7 @@ def mine_chain(self): self.log.info('Create some old blocks') for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 156, 156): self.bump_mocktime(156) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) + self.generate(self.wallet, 1, sync_fun=self.no_op) mining_info = self.nodes[0].getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['currentblocktx'], 0) @@ -70,8 +72,9 @@ def mine_chain(self): self.connect_nodes(1, 0) def run_test(self): - self.mine_chain() node = self.nodes[0] + self.wallet = MiniWallet(node) + self.mine_chain() def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() From 1c87d1c4bc73d8c2aeeed6032e899b3db9ebbaab Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:46:47 +0100 Subject: [PATCH 12/15] merge bitcoin#23836: Expose block filters follow-ups --- src/rest.cpp | 25 ++++++++++++------------- test/functional/interface_rest.py | 10 ++++++++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/rest.cpp b/src/rest.cpp index 961891c49b316..6899e10948f87 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -194,7 +194,7 @@ static bool rest_headers(const CoreContext& context, const auto parsed_count{ToIntegral(path[0])}; if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { - return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0])); + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0])); } std::string hashStr = path[1]; @@ -340,11 +340,10 @@ static bool rest_block_notxdetails(const CoreContext& context, HTTPRequest* req, return rest_block(context, req, strURIPart, false); } - static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart) { - if (!CheckWarmup(req)) - return false; + if (!CheckWarmup(req)) return false; + std::string param; const RetFormat rf = ParseDataFormat(param, strURIPart); @@ -370,10 +369,10 @@ static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, con const auto parsed_count{ToIntegral(uri_parts[1])}; if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { - return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1])); + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1])); } - std::vector headers; + std::vector headers; headers.reserve(*parsed_count); { ChainstateManager* maybe_chainman = GetChainman(context, req); @@ -394,7 +393,7 @@ static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, con std::vector filter_headers; filter_headers.reserve(*parsed_count); - for (const CBlockIndex *pindex : headers) { + for (const CBlockIndex* pindex : headers) { uint256 filter_header; if (!index->LookupFilterHeader(pindex, filter_header)) { std::string errmsg = "Filter not found."; @@ -412,7 +411,7 @@ static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, con switch (rf) { case RetFormat::BINARY: { - CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION}; for (const uint256& header : filter_headers) { ssHeader << header; } @@ -423,7 +422,7 @@ static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, con return true; } case RetFormat::HEX: { - CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION}; for (const uint256& header : filter_headers) { ssHeader << header; } @@ -452,8 +451,8 @@ static bool rest_filter_header(const CoreContext& context, HTTPRequest* req, con static bool rest_block_filter(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart) { - if (!CheckWarmup(req)) - return false; + if (!CheckWarmup(req)) return false; + std::string param; const RetFormat rf = ParseDataFormat(param, strURIPart); @@ -511,7 +510,7 @@ static bool rest_block_filter(const CoreContext& context, HTTPRequest* req, cons switch (rf) { case RetFormat::BINARY: { - CDataStream ssResp(SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION}; ssResp << filter; std::string binaryResp = ssResp.str(); @@ -520,7 +519,7 @@ static bool rest_block_filter(const CoreContext& context, HTTPRequest* req, cons return true; } case RetFormat::HEX: { - CDataStream ssResp(SER_NETWORK, PROTOCOL_VERSION); + CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION}; ssResp << filter; std::string strHex = HexStr(ssResp) + "\n"; diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index b079a9393fa6d..08f89462408e8 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -283,13 +283,19 @@ def run_test(self): json_obj = self.test_rest_request(f"/headers/5/{bb_hash}") assert_equal(len(json_obj), 5) # now we should have 5 header objects json_obj = self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}") + first_filter_header = json_obj[0] assert_equal(len(json_obj), 5) # now we should have 5 filter header objects - self.test_rest_request(f"/blockfilter/basic/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ) + json_obj = self.test_rest_request(f"/blockfilter/basic/{bb_hash}") + + # Compare with normal RPC blockfilter response + rpc_blockfilter = self.nodes[0].getblockfilter(bb_hash) + assert_equal(first_filter_header, rpc_blockfilter['header']) + assert_equal(json_obj['filter'], rpc_blockfilter['filter']) # Test number parsing for num in ['5a', '-5', '0', '2001', '99999999999999999999999999999999999']: assert_equal( - bytes(f'Header count out of acceptable range (1-2000): {num}\r\n', 'ascii'), + bytes(f'Header count is invalid or out of acceptable range (1-2000): {num}\r\n', 'ascii'), self.test_rest_request(f"/headers/{num}/{bb_hash}", ret_type=RetType.BYTES, status=400), ) From 9fec1be9f7c762845a3c558ef79e87cbbd294093 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 31 Jan 2022 22:25:14 +0100 Subject: [PATCH 13/15] merge bitcoin#24223: use MiniWallet for interface_rest.py The size in bytes of the three transactions used for testing reduces from 225 each to 85 each, so the size of memory pool will no longer be 674, it will be 255, which is 45 short of the minimum 300 expected by the test. So the minimum threshold has been reduced to 240 (assuming the transaction will at a minimum, consume 80 bytes). --- test/functional/interface_rest.py | 57 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 08f89462408e8..1b69e45018e87 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -6,21 +6,28 @@ from decimal import Decimal from enum import Enum +import http.client from io import BytesIO import json from struct import pack, unpack - -import http.client import urllib.parse + +from test_framework.messages import ( + BLOCK_HEADER_SIZE, + COIN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, ) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) -from test_framework.messages import BLOCK_HEADER_SIZE INVALID_PARAM = "abc" UNKNOWN_PARAM = "0000000000000000000000000000000000000000000000000000000000000000" @@ -43,14 +50,13 @@ def filter_output_indices_by_value(vouts, value): class RESTTest (BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [["-rest", "-blockfilterindex=1"], []] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON): rest_uri = '/rest' + uri if req_type == ReqType.JSON: @@ -79,17 +85,11 @@ def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body= def run_test(self): self.url = urllib.parse.urlparse(self.nodes[0].url) - self.log.info("Mine blocks and send Dash to node 1") + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() - # Random address so node1's balance doesn't increase - not_related_address = "yj949n1UH6fDhw6HtVE5VMj2iSTaSWBMcW" - - self.generate(self.nodes[0], 1) - self.generatetoaddress(self.nodes[1], 100, not_related_address) - - assert_equal(self.nodes[0].getbalance(), 500) - - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + self.log.info("Broadcast test transaction and sync nodes") + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) self.sync_all() self.log.info("Test the /tx URI") @@ -115,11 +115,9 @@ def run_test(self): self.log.info("Query an unspent TXO using the /getutxos URI") - self.generatetoaddress(self.nodes[1], 1, not_related_address) + self.generate(self.wallet, 1) bb_hash = self.nodes[0].getbestblockhash() - assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) - # Check chainTip response json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}") assert_equal(json_obj['chaintipHash'], bb_hash) @@ -161,7 +159,7 @@ def run_test(self): response_hash = output.read(32)[::-1].hex() assert_equal(bb_hash, response_hash) # check if getutxo's chaintip during calculation was fine - assert_equal(chain_height, 102) # chain height must be 102 + assert_equal(chain_height, 201) # chain height must be 201 (pre-mined chain [200] + generated block [1]) self.log.info("Test the /getutxos URI with and without /checkmempool") # Create a transaction, check that it's found with /checkmempool, but @@ -169,7 +167,7 @@ def run_test(self): # found with or without /checkmempool. # do a tx and don't sync - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) json_obj = self.test_rest_request(f"/tx/{txid}") # get the spent output to later check for utxo (should be spent by then) spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']) @@ -301,18 +299,20 @@ def run_test(self): self.log.info("Test tx inclusion in the /mempool and /block URIs") - # Make 3 tx and mine them on node 1 + # Make 3 chained txs and mine them on node 1 txs = [] - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) + input_txid = txid + for _ in range(3): + utxo_to_spend = self.wallet.get_utxo(txid=input_txid) + txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['txid']) + input_txid = txs[-1] self.sync_all() # Check that there are exactly 3 transactions in the TX memory pool before generating the block json_obj = self.test_rest_request("/mempool/info") assert_equal(json_obj['size'], 3) - # the size of the memory pool should be greater than 3x ~100 bytes - assert_greater_than(json_obj['bytes'], 300) + # the size of the memory pool should be greater than 3x ~80 bytes + assert_greater_than(json_obj['bytes'], 240) mempool_info = self.nodes[0].getmempoolinfo() assert_equal(json_obj, mempool_info) @@ -353,5 +353,6 @@ def run_test(self): blockchain_info = self.nodes[0].getblockchaininfo() assert_equal(blockchain_info, json_obj) + if __name__ == '__main__': RESTTest().main() From eba742dfa3a23df2bcee73ef4179a01280603fe7 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 11 Mar 2022 16:09:54 +0100 Subject: [PATCH 14/15] merge bitcoin#24533: use MiniWallet for feature_maxuploadtarget.py --- test/functional/feature_maxuploadtarget.py | 27 +++++++++++++--------- test/functional/test_framework/util.py | 16 ++++++------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 4c9f37129dd3c..59c7f320dcddb 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -12,10 +12,20 @@ """ from collections import defaultdict -from test_framework.messages import CInv, MAX_BLOCK_SIZE, MSG_BLOCK, msg_getdata +from test_framework.messages import ( + CInv, + MAX_BLOCK_SIZE, + MSG_BLOCK, + msg_getdata, +) from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, mine_large_block, set_node_times +from test_framework.util import ( + assert_equal, + mine_large_block, + set_node_times, +) +from test_framework.wallet import MiniWallet class TestP2PConn(P2PInterface): @@ -41,12 +51,6 @@ def set_test_params(self): ]] self.supports_cli = False - # Cache for utxos, as the listunspent may take a long time later in the test - self.utxo_cache = [] - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): # Advance all nodes 2 weeks in the future old_mocktime = self.mocktime @@ -60,7 +64,8 @@ def run_test(self): self.nodes[0].setmocktime(old_mocktime) # Generate some old blocks - self.generate(self.nodes[0], 130) + self.wallet = MiniWallet(self.nodes[0]) + self.generate(self.wallet, 130) # p2p_conns[0] will only request old blocks # p2p_conns[1] will only request new blocks @@ -72,7 +77,7 @@ def run_test(self): p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn(), supports_v2_p2p=False)) # Now mine a big block - mine_large_block(self, self.nodes[0], self.utxo_cache) + mine_large_block(self, self.wallet, self.nodes[0]) # Store the hash; we'll request this later big_old_block = self.nodes[0].getbestblockhash() @@ -83,7 +88,7 @@ def run_test(self): self.nodes[0].setmocktime(current_mocktime - 2*60*60*24) # Mine one more block, so that the prior block looks old - mine_large_block(self, self.nodes[0], self.utxo_cache) + mine_large_block(self, self.wallet, self.nodes[0]) # We'll be requesting this new block too big_new_block = self.nodes[0].getbestblockhash() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 1d730feb8cc39..dea37a7c55414 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -644,17 +644,17 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee): return txids -def mine_large_block(test_framework, node, utxos=None): +def mine_large_block(test_framework, mini_wallet, node): # generate a 66k transaction, # and 14 of them is close to the 1MB block limit - num = 14 txouts = gen_return_txouts() - utxos = utxos if utxos is not None else [] - if len(utxos) < num: - utxos.clear() - utxos.extend(node.listunspent()) - fee = 100 * node.getnetworkinfo()["relayfee"] - create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee) + from .messages import COIN + fee = 100 * int(node.getnetworkinfo()["relayfee"] * COIN) + for _ in range(14): + tx = mini_wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx'] + tx.vout[0].nValue -= fee + tx.vout.extend(txouts) + mini_wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex()) test_framework.generate(node, 1) From 14adbf54669f23129f5ce76d413f353a56ba6c79 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 17 Mar 2022 20:07:47 +0100 Subject: [PATCH 15/15] merge bitcoin#24605: Use MiniWallet in feature_coinstatsindex tx2 will spend 20.999 tDASH instead of 20.99 tDASH as the remnant (0.01 tDASH) would result in a "Fee exceeds maximum configured by user" error. A 0.001 tDASH fee is permissible. --- test/functional/feature_coinstatsindex.py | 65 +++++++++++------------ test/functional/test_framework/wallet.py | 21 +++++--- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index 9c3d8523d6d72..0aad2a201ae0b 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -16,12 +16,10 @@ from test_framework.blocktools import ( create_block, create_coinbase, + COINBASE_MATURITY, ) from test_framework.messages import ( COIN, - COutPoint, - CTransaction, - CTxIn, CTxOut, ) from test_framework.script import ( @@ -34,6 +32,11 @@ assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) + class CoinStatsIndexTest(BitcoinTestFramework): def set_test_params(self): @@ -45,10 +48,8 @@ def set_test_params(self): ["-coinstatsindex"] ] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self._test_coin_stats_index() self._test_use_index_option() self._test_reorg_index() @@ -81,9 +82,8 @@ def _test_coin_stats_index(self): index_hash_options = ['none', 'muhash'] # Generate a normal transaction and mine it - self.generate(node, 101) - address = self.nodes[0].get_deterministic_priv_key().address - node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True) + self.generate(self.wallet, COINBASE_MATURITY + 1) + self.wallet.send_self_transfer(from_node=node) self.generate(node, 1) self.sync_blocks(timeout=120) @@ -150,36 +150,31 @@ def _test_coin_stats_index(self): assert_equal(res5['block_info'], { 'unspendable': 0, 'prevout_spent': 500, - 'new_outputs_ex_coinbase': Decimal('499.99999775'), - 'coinbase': Decimal('500.00000225'), + 'new_outputs_ex_coinbase': Decimal('499.99974500'), + 'coinbase': Decimal('500.00025500'), 'unspendables': { 'genesis_block': 0, 'bip30': 0, 'scripts': 0, - 'unclaimed_rewards': 0 + 'unclaimed_rewards': 0, } }) self.block_sanity_check(res5['block_info'], 101) # Generate and send a normal tx with two outputs - tx1_inputs = [] - tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42} - raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs) - funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1) - signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex']) - tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex']) + tx1_txid, tx1_vout = self.wallet.send_to( + from_node=node, + scriptPubKey=self.wallet.get_scriptPubKey(), + amount=21 * COIN, + ) # Find the right position of the 21 BTC output - tx1_final = self.nodes[0].gettransaction(tx1_txid) - for output in tx1_final['details']: - if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive': - n = output['vout'] + tx1_out_21 = self.wallet.get_utxo(txid=tx1_txid, vout=tx1_vout) # Generate and send another tx with an OP_RETURN output (which is unspendable) - tx2 = CTransaction() - tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b'')) - tx2.vout.append(CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE]*30))) - tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())['hex'] + tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx'] + tx2.vout = [CTxOut(int(Decimal('20.999') * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx2_hex = tx2.serialize().hex() self.nodes[0].sendrawtransaction(tx2_hex) # Include both txs in a block @@ -188,17 +183,17 @@ def _test_coin_stats_index(self): for hash_option in index_hash_options: # Check all amounts were registered correctly res6 = index_node.gettxoutsetinfo(hash_option, 108) - assert_equal(res6['total_unspendable_amount'], Decimal('70.99000000')) + assert_equal(res6['total_unspendable_amount'], Decimal('70.99900000')) assert_equal(res6['block_info'], { - 'unspendable': Decimal('20.99000000'), - 'prevout_spent': 511, - 'new_outputs_ex_coinbase': Decimal('489.99999741'), - 'coinbase': Decimal('500.01000259'), + 'unspendable': Decimal('20.99900000'), + 'prevout_spent': 521, + 'new_outputs_ex_coinbase': Decimal('499.99999000'), + 'coinbase': Decimal('500.00101000'), 'unspendables': { 'genesis_block': 0, 'bip30': 0, - 'scripts': Decimal('20.99000000'), - 'unclaimed_rewards': 0 + 'scripts': Decimal('20.99900000'), + 'unclaimed_rewards': 0, } }) self.block_sanity_check(res6['block_info'], 107) @@ -219,7 +214,7 @@ def _test_coin_stats_index(self): for hash_option in index_hash_options: res7 = index_node.gettxoutsetinfo(hash_option, 109) - assert_equal(res7['total_unspendable_amount'], Decimal('530.99000000')) + assert_equal(res7['total_unspendable_amount'], Decimal('530.99900000')) assert_equal(res7['block_info'], { 'unspendable': 460, 'prevout_spent': 0, @@ -279,7 +274,7 @@ def _test_reorg_index(self): # Generate two block, let the index catch up, then invalidate the blocks index_node = self.nodes[1] - reorg_blocks = self.generatetoaddress(index_node, 2, index_node.getnewaddress()) + reorg_blocks = self.generatetoaddress(index_node, 2, getnewdestination()[2]) reorg_block = reorg_blocks[1] res_invalid = index_node.gettxoutsetinfo('muhash') index_node.invalidateblock(reorg_blocks[0]) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 811a44a3228c0..8b87c7a657430 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -7,6 +7,11 @@ from copy import deepcopy from decimal import Decimal from enum import Enum +from random import choice +from typing import ( + Any, + Optional, +) from test_framework.address import ( base58_to_byte, key_to_p2pkh, @@ -14,8 +19,6 @@ ) from test_framework.descriptors import descsum_create from test_framework.key import ECKey -from random import choice -from typing import Optional from test_framework.messages import ( COIN, COutPoint, @@ -132,24 +135,30 @@ def generate(self, num_blocks, **kwargs): self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']}) return blocks + def get_scriptPubKey(self): + return self._scriptPubKey + def get_descriptor(self): return descsum_create(f'raw({self._scriptPubKey.hex()})') def get_address(self): return self._address - def get_utxo(self, *, txid: Optional[str]='', mark_as_spent=True): + def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True): """ Returns a utxo and marks it as spent (pops it from the internal list) Args: txid: get the first utxo we find from a specific transaction """ - index = -1 # by default the last utxo self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last if txid: - utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos)) - index = self._utxos.index(utxo) + utxo_filter: Any = filter(lambda utxo: txid == utxo['txid'], self._utxos) + else: + utxo_filter = reversed(self._utxos) # By default the largest utxo + if vout is not None: + utxo_filter = filter(lambda utxo: vout == utxo['vout'], utxo_filter) + index = self._utxos.index(next(utxo_filter)) if mark_as_spent: return self._utxos.pop(index) else: