diff options
Diffstat (limited to 'test')
44 files changed, 597 insertions, 224 deletions
diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index 5f3e725d4c..4a15ae8792 100644 --- a/test/functional/data/rpc_decodescript.json +++ b/test/functional/data/rpc_decodescript.json @@ -69,7 +69,7 @@ "p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM", "segwit": { "asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", - "desc": "wsh(raw(02eeee))#gtay4y0z", + "desc": "addr(bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5)#5akkdska", "hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", "address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5", "type": "witness_v0_scripthash", diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py index afee9597ad..740d3b7f0e 100755 --- a/test/functional/feature_abortnode.py +++ b/test/functional/feature_abortnode.py @@ -25,7 +25,7 @@ class AbortNodeTest(BitcoinTestFramework): self.generate(self.nodes[0], 3, sync_fun=self.no_op) # Deleting the undo file will result in reorg failure - (self.nodes[0].chain_path / "blocks" / "rev00000.dat").unlink() + (self.nodes[0].blocks_path / "rev00000.dat").unlink() # Connecting to a node with a more work chain will trigger a reorg # attempt. @@ -36,7 +36,7 @@ class AbortNodeTest(BitcoinTestFramework): # Check that node0 aborted self.log.info("Waiting for crash") - self.nodes[0].wait_until_stopped(timeout=5, expect_error=True) + self.nodes[0].wait_until_stopped(timeout=5, expect_error=True, expected_stderr="Error: A fatal internal error occurred, see debug.log for details") self.log.info("Node crashed - now verifying restart fails") self.nodes[0].assert_start_raises_init_error() diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py index 7877f9d302..d901b7bcf9 100755 --- a/test/functional/feature_addrman.py +++ b/test/functional/feature_addrman.py @@ -32,12 +32,12 @@ def serialize_addrman( r += struct.pack("B", format) r += struct.pack("B", INCOMPATIBILITY_BASE + lowest_compatible) r += ser_uint256(bucket_key) - r += struct.pack("i", len_new or len(new)) - r += struct.pack("i", len_tried or len(tried)) + r += struct.pack("<i", len_new or len(new)) + r += struct.pack("<i", len_tried or len(tried)) ADDRMAN_NEW_BUCKET_COUNT = 1 << 10 - r += struct.pack("i", ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30)) + r += struct.pack("<i", ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30)) for _ in range(ADDRMAN_NEW_BUCKET_COUNT): - r += struct.pack("i", 0) + r += struct.pack("<i", 0) checksum = hash256(r) r += mock_checksum or checksum return r diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 765db97445..58ef1e761d 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -43,8 +43,7 @@ from test_framework.script import ( OP_INVALIDOPCODE, OP_RETURN, OP_TRUE, - SIGHASH_ALL, - LegacySignatureHash, + sign_input_legacy, ) from test_framework.script_util import ( script_to_p2sh_script, @@ -539,12 +538,8 @@ class FullBlockTest(BitcoinTestFramework): # second input is corresponding P2SH output from b39 tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b'')) # Note: must pass the redeem_script (not p2sh_script) to the signature hash function - (sighash, err) = LegacySignatureHash(redeem_script, tx, 1, SIGHASH_ALL) - sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) - scriptSig = CScript([sig, redeem_script]) - - tx.vin[1].scriptSig = scriptSig - tx.rehash() + tx.vin[1].scriptSig = CScript([redeem_script]) + sign_input_legacy(tx, 1, redeem_script, self.coinbase_key) new_txs.append(tx) lastOutpoint = COutPoint(tx.sha256, 0) @@ -1338,8 +1333,7 @@ class FullBlockTest(BitcoinTestFramework): if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend tx.vin[0].scriptSig = CScript() return - (sighash, err) = LegacySignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + sign_input_legacy(tx, 0, spend_tx.vout[0].scriptPubKey, self.coinbase_key) def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, 0, value, script) diff --git a/test/functional/feature_dirsymlinks.py b/test/functional/feature_dirsymlinks.py index 288754c04c..96f4aed08a 100755 --- a/test/functional/feature_dirsymlinks.py +++ b/test/functional/feature_dirsymlinks.py @@ -26,7 +26,7 @@ class SymlinkTest(BitcoinTestFramework): self.stop_node(0) rename_and_link( - from_name=self.nodes[0].chain_path / "blocks", + from_name=self.nodes[0].blocks_path, to_name=dir_new_blocks, ) rename_and_link( diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index 77a056346a..d6e802b399 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test indices in conjunction with prune.""" +import os from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -127,8 +128,9 @@ class FeatureIndexPruneTest(BitcoinTestFramework): self.log.info("make sure we get an init error when starting the nodes again with the indices") filter_msg = "Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" stats_msg = "Error: coinstatsindex best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" + end_msg = f"{os.linesep}Error: Failed to start indexes, shutting down.." for i, msg in enumerate([filter_msg, stats_msg, filter_msg]): - self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg) + self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg+end_msg) self.log.info("make sure the nodes start again with the indices and an additional -reindex arg") for i in range(3): diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 7af67730bd..64ca312b84 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -71,7 +71,7 @@ class InitStressTest(BitcoinTestFramework): b'init message: Starting network threads', b'net thread start', b'addcon thread start', - b'loadblk thread start', + b'initload thread start', b'txindex thread start', b'block filter index thread start', b'coinstatsindex thread start', diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py index c90ccc4936..12d65fde68 100755 --- a/test/functional/feature_loadblock.py +++ b/test/functional/feature_loadblock.py @@ -37,7 +37,7 @@ class LoadblockTest(BitcoinTestFramework): cfg_file = os.path.join(data_dir, "linearize.cfg") bootstrap_file = os.path.join(self.options.tmpdir, "bootstrap.dat") genesis_block = self.nodes[0].getblockhash(0) - blocks_dir = self.nodes[0].chain_path / "blocks" + blocks_dir = self.nodes[0].blocks_path hash_list = tempfile.NamedTemporaryFile(dir=data_dir, mode='w', delete=False, diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py index fcbb49d420..83f1c5003c 100755 --- a/test/functional/feature_reindex.py +++ b/test/functional/feature_reindex.py @@ -38,7 +38,7 @@ class ReindexTest(BitcoinTestFramework): # In this test environment, blocks will always be in order (since # we're generating them rather than getting them from peers), so to # test out-of-order handling, swap blocks 1 and 2 on disk. - blk0 = self.nodes[0].chain_path / "blocks" / "blk00000.dat" + blk0 = self.nodes[0].blocks_path / "blk00000.dat" with open(blk0, 'r+b') as bf: # Read at least the first few blocks (including genesis) b = bf.read(2000) diff --git a/test/functional/feature_remove_pruned_files_on_startup.py b/test/functional/feature_remove_pruned_files_on_startup.py index a55e08ef1a..c128587949 100755 --- a/test/functional/feature_remove_pruned_files_on_startup.py +++ b/test/functional/feature_remove_pruned_files_on_startup.py @@ -20,10 +20,10 @@ class FeatureRemovePrunedFilesOnStartupTest(BitcoinTestFramework): self.sync_blocks() def run_test(self): - blk0 = self.nodes[0].chain_path / "blocks" / "blk00000.dat" - rev0 = self.nodes[0].chain_path / "blocks" / "rev00000.dat" - blk1 = self.nodes[0].chain_path / "blocks" / "blk00001.dat" - rev1 = self.nodes[0].chain_path / "blocks" / "rev00001.dat" + blk0 = self.nodes[0].blocks_path / "blk00000.dat" + rev0 = self.nodes[0].blocks_path / "rev00000.dat" + blk1 = self.nodes[0].blocks_path / "blk00001.dat" + rev1 = self.nodes[0].blocks_path / "rev00001.dat" self.mine_batches(800) fo1 = os.open(blk0, os.O_RDONLY) fo2 = os.open(rev1, os.O_RDONLY) diff --git a/test/functional/feature_txindex_compatibility.py b/test/functional/feature_txindex_compatibility.py index a5b25cbd71..572e12df13 100755 --- a/test/functional/feature_txindex_compatibility.py +++ b/test/functional/feature_txindex_compatibility.py @@ -11,16 +11,16 @@ import os import shutil from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error from test_framework.wallet import MiniWallet class TxindexCompatibilityTest(BitcoinTestFramework): def set_test_params(self): - self.num_nodes = 3 + self.num_nodes = 2 self.extra_args = [ ["-reindex", "-txindex"], [], - [], ] def skip_test_if_missing_module(self): @@ -33,12 +33,10 @@ class TxindexCompatibilityTest(BitcoinTestFramework): versions=[ 160300, # Last release with legacy txindex None, # For MiniWallet, without migration code - 220000, # Last release with migration code (0.17.x - 22.x) ], ) self.start_nodes() self.connect_nodes(0, 1) - self.connect_nodes(1, 2) def run_test(self): mini_wallet = MiniWallet(self.nodes[1]) @@ -47,22 +45,12 @@ class TxindexCompatibilityTest(BitcoinTestFramework): self.generate(self.nodes[1], 1) self.log.info("Check legacy txindex") + assert_raises_rpc_error(-5, "Use -txindex", lambda: self.nodes[1].getrawtransaction(txid=spend_utxo["txid"])) self.nodes[0].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex self.stop_nodes() legacy_chain_dir = self.nodes[0].chain_path - self.log.info("Migrate legacy txindex") - migrate_chain_dir = self.nodes[2].chain_path - shutil.rmtree(migrate_chain_dir) - shutil.copytree(legacy_chain_dir, migrate_chain_dir) - with self.nodes[2].assert_debug_log([ - "Upgrading txindex database...", - "txindex is enabled at height 200", - ]): - self.start_node(2, extra_args=["-txindex"]) - self.nodes[2].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex - self.log.info("Drop legacy txindex") drop_index_chain_dir = self.nodes[1].chain_path shutil.rmtree(drop_index_chain_dir) @@ -73,16 +61,14 @@ class TxindexCompatibilityTest(BitcoinTestFramework): ) # Build txindex from scratch and check there is no error this time self.start_node(1, extra_args=["-txindex"]) - self.nodes[2].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex + self.wait_until(lambda: self.nodes[1].getindexinfo()["txindex"]["synced"] == True) + self.nodes[1].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex self.stop_nodes() self.log.info("Check migrated txindex cannot be read by legacy node") err_msg = f": You need to rebuild the database using -reindex to change -txindex.{os.linesep}Please restart with -reindex or -reindex-chainstate to recover." shutil.rmtree(legacy_chain_dir) - shutil.copytree(migrate_chain_dir, legacy_chain_dir) - self.nodes[0].assert_start_raises_init_error(extra_args=["-txindex"], expected_msg=err_msg) - shutil.rmtree(legacy_chain_dir) shutil.copytree(drop_index_chain_dir, legacy_chain_dir) self.nodes[0].assert_start_raises_init_error(extra_args=["-txindex"], expected_msg=err_msg) diff --git a/test/functional/feature_unsupported_utxo_db.py b/test/functional/feature_unsupported_utxo_db.py index 1c8c08d1d8..6acf551216 100755 --- a/test/functional/feature_unsupported_utxo_db.py +++ b/test/functional/feature_unsupported_utxo_db.py @@ -40,9 +40,9 @@ class UnsupportedUtxoDbTest(BitcoinTestFramework): self.log.info("Check init error") legacy_utxos_dir = self.nodes[0].chain_path / "chainstate" - legacy_blocks_dir = self.nodes[0].chain_path / "blocks" + legacy_blocks_dir = self.nodes[0].blocks_path recent_utxos_dir = self.nodes[1].chain_path / "chainstate" - recent_blocks_dir = self.nodes[1].chain_path / "blocks" + recent_blocks_dir = self.nodes[1].blocks_path shutil.copytree(legacy_utxos_dir, recent_utxos_dir) shutil.copytree(legacy_blocks_dir, recent_blocks_dir) self.nodes[1].assert_start_raises_init_error( diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py index f138fa44cc..67cf649830 100755 --- a/test/functional/interface_usdt_mempool.py +++ b/test/functional/interface_usdt_mempool.py @@ -307,7 +307,10 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:rejected event was handled successfully...") assert_equal(EXPECTED_REJECTED_EVENTS, handled_rejected_events) assert_equal(bytes(event.hash)[::-1].hex(), tx["tx"].hash) - assert_equal(event.reason.decode("UTF-8"), "min relay fee not met") + # The next test is already known to fail, so disable it to avoid + # wasting CPU time and developer time. See + # https://github.com/bitcoin/bitcoin/issues/27380 + #assert_equal(event.reason.decode("UTF-8"), "min relay fee not met") bpf.cleanup() self.generate(self.wallet, 1) diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index 3f632d3d56..fd3e219586 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -10,8 +10,6 @@ In case we need to break mempool compatibility we can continue to use the test b Previous releases are required by this test, see test/README.md. """ -import os - from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.wallet import ( @@ -23,6 +21,7 @@ from test_framework.wallet import ( class MempoolCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_previous_releases() @@ -55,9 +54,9 @@ class MempoolCompatibilityTest(BitcoinTestFramework): self.stop_node(1) self.log.info("Move mempool.dat from old to new node") - old_node_mempool = os.path.join(old_node.chain_path, 'mempool.dat') - new_node_mempool = os.path.join(new_node.chain_path, 'mempool.dat') - os.rename(old_node_mempool, new_node_mempool) + old_node_mempool = old_node.chain_path / "mempool.dat" + new_node_mempool = new_node.chain_path / "mempool.dat" + old_node_mempool.rename(new_node_mempool) self.log.info("Start new node and verify mempool contains the tx") self.start_node(1) @@ -70,7 +69,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): self.stop_node(1) self.log.info("Move mempool.dat from new to old node") - os.rename(new_node_mempool, old_node_mempool) + new_node_mempool.rename(old_node_mempool) self.log.info("Start old node again and verify mempool contains both txs") self.start_node(0, ['-nowallet']) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index aabf06ee53..56cd615dac 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -18,9 +18,10 @@ from test_framework.blocktools import ( TIME_GENESIS_BLOCK, ) from test_framework.messages import ( + BLOCK_HEADER_SIZE, CBlock, CBlockHeader, - BLOCK_HEADER_SIZE, + COIN, ser_uint256, ) from test_framework.p2p import P2PDataStore @@ -28,12 +29,14 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, + get_fee, ) from test_framework.wallet import MiniWallet VERSIONBITS_TOP_BITS = 0x20000000 VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28 +DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB] def assert_template(node, block, expect, rehash=True): @@ -73,6 +76,45 @@ class MiningTest(BitcoinTestFramework): self.restart_node(0) self.connect_nodes(0, 1) + def test_blockmintxfee_parameter(self): + self.log.info("Test -blockmintxfee setting") + self.restart_node(0, extra_args=['-minrelaytxfee=0', '-persistmempool=0']) + node = self.nodes[0] + + # test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB] + for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 50, 100, 500, 2500, 5000, 21000, 333333, 2500000): + blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN) + if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE: + self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...") + else: + blockmintxfee_parameter = f"-blockmintxfee={blockmintxfee_btc_kvb:.8f}" + self.log.info(f"-> Test {blockmintxfee_parameter} ({blockmintxfee_sat_kvb} sat/kvB)...") + self.restart_node(0, extra_args=[blockmintxfee_parameter, '-minrelaytxfee=0', '-persistmempool=0']) + self.wallet.rescan_utxos() # to avoid spending outputs of txs that are not in mempool anymore after restart + + # submit one tx with exactly the blockmintxfee rate, and one slightly below + tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb) + assert_equal(tx_with_min_feerate["fee"], get_fee(tx_with_min_feerate["tx"].get_vsize(), blockmintxfee_btc_kvb)) + if blockmintxfee_btc_kvb > 0: + lowerfee_btc_kvb = blockmintxfee_btc_kvb - Decimal(10)/COIN # 0.01 sat/vbyte lower + tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=lowerfee_btc_kvb) + assert_equal(tx_below_min_feerate["fee"], get_fee(tx_below_min_feerate["tx"].get_vsize(), lowerfee_btc_kvb)) + else: # go below zero fee by using modified fees + tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb) + node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1) + + # check that tx below specified fee-rate is neither in template nor in the actual block + block_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + block_template_txids = [tx['txid'] for tx in block_template['transactions']] + self.generate(self.wallet, 1, sync_fun=self.no_op) + block = node.getblock(node.getbestblockhash(), verbosity=2) + block_txids = [tx['txid'] for tx in block['tx']] + + assert tx_with_min_feerate['txid'] in block_template_txids + assert tx_with_min_feerate['txid'] in block_txids + assert tx_below_min_feerate['txid'] not in block_template_txids + assert tx_below_min_feerate['txid'] not in block_txids + def run_test(self): node = self.nodes[0] self.wallet = MiniWallet(node) @@ -279,6 +321,8 @@ class MiningTest(BitcoinTestFramework): node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid + self.test_blockmintxfee_parameter() + if __name__ == '__main__': MiningTest().main() diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py index 1c9ad7289b..60b43c32ae 100755 --- a/test/functional/p2p_getaddr_caching.py +++ b/test/functional/p2p_getaddr_caching.py @@ -6,7 +6,6 @@ import time -from test_framework.messages import msg_getaddr from test_framework.p2p import ( P2PInterface, p2p_lock @@ -21,6 +20,7 @@ from test_framework.util import ( MAX_ADDR_TO_SEND = 1000 MAX_PCT_ADDR_TO_SEND = 23 + class AddrReceiver(P2PInterface): def __init__(self): @@ -70,11 +70,8 @@ class AddrTest(BitcoinTestFramework): cur_mock_time = int(time.time()) for i in range(N): addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver()) - addr_receiver_local.send_and_ping(msg_getaddr()) addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1) - addr_receiver_onion1.send_and_ping(msg_getaddr()) addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2) - addr_receiver_onion2.send_and_ping(msg_getaddr()) # Trigger response cur_mock_time += 5 * 60 @@ -105,11 +102,8 @@ class AddrTest(BitcoinTestFramework): self.log.info('After time passed, see a new response to addr request') addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver()) - addr_receiver_local.send_and_ping(msg_getaddr()) addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1) - addr_receiver_onion1.send_and_ping(msg_getaddr()) addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2) - addr_receiver_onion2.send_and_ping(msg_getaddr()) # Trigger response cur_mock_time += 5 * 60 @@ -123,5 +117,6 @@ class AddrTest(BitcoinTestFramework): assert set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs()) assert set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs()) + if __name__ == '__main__': AddrTest().main() diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py index 626422370a..32a23532a2 100755 --- a/test/functional/p2p_invalid_locator.py +++ b/test/functional/p2p_invalid_locator.py @@ -32,7 +32,7 @@ class InvalidLocatorTest(BitcoinTestFramework): within_max_peer = node.add_p2p_connection(P2PInterface()) msg.locator.vHave = [int(node.getblockhash(i - 1), 16) for i in range(block_count, block_count - (MAX_LOCATOR_SZ), -1)] within_max_peer.send_message(msg) - if type(msg) == msg_getheaders: + if type(msg) is msg_getheaders: within_max_peer.wait_for_header(node.getbestblockhash()) else: within_max_peer.wait_for_block(int(node.getbestblockhash(), 16)) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index bfae190c66..1e7bc95a63 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -71,8 +71,8 @@ from test_framework.script import ( SIGHASH_NONE, SIGHASH_SINGLE, SegwitV0SignatureHash, - LegacySignatureHash, hash160, + sign_input_legacy, ) from test_framework.script_util import ( key_to_p2pk_script, @@ -1529,10 +1529,8 @@ class SegWitTest(BitcoinTestFramework): tx5 = CTransaction() tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE]))) - (sig_hash, err) = LegacySignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) - signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL - tx5.vin[0].scriptSig = CScript([signature, pubkey]) - tx5.rehash() + tx5.vin[0].scriptSig = CScript([pubkey]) + sign_input_legacy(tx5, 0, script_pubkey, key) # Should pass policy and consensus. test_transaction_acceptance(self.nodes[0], self.test_node, tx5, True, True) block = self.build_next_block() diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 5f2bece733..18a0a0c6cc 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -577,8 +577,8 @@ class BlockchainTest(BitcoinTestFramework): self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data") def move_block_file(old, new): - old_path = self.nodes[0].chain_path / "blocks" / old - new_path = self.nodes[0].chain_path / "blocks" / new + old_path = self.nodes[0].blocks_path / old + new_path = self.nodes[0].blocks_path / new old_path.rename(new_path) # Move instead of deleting so we can restore chain state afterwards diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 673836bd04..f37e61ab50 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -271,7 +271,7 @@ class DecodeScriptTest(BitcoinTestFramework): assert res["segwit"]["desc"] == "wsh(and_v(and_v(v:hash160(ffffffffffffffffffffffffffffffffffffffff),v:pk(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0)),older(1)))#gm8xz4fl" # Miniscript-incompatible offered HTLC res = self.nodes[0].decodescript("82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2") - assert res["segwit"]["desc"] == "wsh(raw(82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2))#ra6w2xa7" + assert res["segwit"]["desc"] == "addr(bcrt1q73qyfypp47hvgnkjqnav0j3k2lq3v76wg22dk8tmwuz5sfgv66xsvxg6uu)#9p3q328s" # Miniscript-compatible multisig bigger than 520 byte P2SH limit. res = self.nodes[0].decodescript("5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68") assert_equal(res["segwit"]["desc"], "wsh(or_d(multi(11,020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb,03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a),and_v(v:older(4032),multi(2,03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb))))#7jwwklk4") diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py index 4260e95629..2cae602cc2 100755 --- a/test/functional/rpc_dumptxoutset.py +++ b/test/functional/rpc_dumptxoutset.py @@ -4,13 +4,15 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the generation of UTXO snapshots using `dumptxoutset`. """ +from pathlib import Path from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error - -import hashlib -from pathlib import Path +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + sha256sum_file, +) class DumptxoutsetTest(BitcoinTestFramework): @@ -39,11 +41,10 @@ class DumptxoutsetTest(BitcoinTestFramework): out['base_hash'], '09abf0e7b510f61ca6cf33bab104e9ee99b3528b371d27a2d4b39abb800fba7e') - with open(str(expected_path), 'rb') as f: - digest = hashlib.sha256(f.read()).hexdigest() - # UTXO snapshot hash should be deterministic based on mocked time. - assert_equal( - digest, 'b1bacb602eacf5fbc9a7c2ef6eeb0d229c04e98bdf0c2ea5929012cd0eae3830') + # UTXO snapshot hash should be deterministic based on mocked time. + assert_equal( + sha256sum_file(str(expected_path)).hex(), + 'b1bacb602eacf5fbc9a7c2ef6eeb0d229c04e98bdf0c2ea5929012cd0eae3830') assert_equal( out['txoutset_hash'], '1f7e3befd45dc13ae198dfbb22869a9c5c4196f8e9ef9735831af1288033f890') diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index c4ed4da0f2..b574a370d6 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -327,7 +327,7 @@ class PSBTTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Invalid amount", self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: invalid_value, "add_inputs": True}) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: assert_raises_rpc_error(-3, "Invalid amount", self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": invalid_value, "add_inputs": True}) @@ -883,6 +883,9 @@ class PSBTTest(BitcoinTestFramework): comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()]) assert_equal(comb_psbt, psbt) + self.log.info("Test walletprocesspsbt raises if an invalid sighashtype is passed") + assert_raises_rpc_error(-8, "all is not a valid sighash parameter.", self.nodes[0].walletprocesspsbt, psbt, sighashtype="all") + self.log.info("Test decoding PSBT with per-input preimage types") # note that the decodepsbt RPC doesn't check whether preimages and hashes match hash_ripemd160, preimage_ripemd160 = random_bytes(20), random_bytes(50) @@ -982,5 +985,9 @@ class PSBTTest(BitcoinTestFramework): rawtx = self.nodes[2].finalizepsbt(psbt)["hex"] self.nodes[2].sendrawtransaction(rawtx) + self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed") + assert_raises_rpc_error(-8, "all is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all") + + if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py index ac7a86704f..0913f5057e 100755 --- a/test/functional/rpc_signrawtransactionwithkey.py +++ b/test/functional/rpc_signrawtransactionwithkey.py @@ -14,6 +14,7 @@ from test_framework.address import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, find_vout_for_address, ) from test_framework.script_util import ( @@ -33,6 +34,14 @@ from decimal import ( Decimal, ) +INPUTS = [ + # Valid pay-to-pubkey scripts + {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, + 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, + {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0, + 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, +] +OUTPUTS = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} class SignRawTransactionWithKeyTest(BitcoinTestFramework): def set_test_params(self): @@ -47,6 +56,11 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0) return txid + def assert_signing_completed_successfully(self, signed_tx): + assert 'errors' not in signed_tx + assert 'complete' in signed_tx + assert_equal(signed_tx['complete'], True) + def successful_signing_test(self): """Create and sign a valid raw transaction with one input. @@ -56,25 +70,10 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): 2) No script verification error occurred""" self.log.info("Test valid raw transaction with one input") privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA'] + rawTx = self.nodes[0].createrawtransaction(INPUTS, OUTPUTS) + rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, INPUTS) - inputs = [ - # Valid pay-to-pubkey scripts - {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, - 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, - {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0, - 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, - ] - - outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} - - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs) - - # 1) The transaction has a complete set of signatures - assert rawTxSigned['complete'] - - # 2) No script verification error occurred - assert 'errors' not in rawTxSigned + self.assert_signing_completed_successfully(rawTxSigned) def witness_script_test(self): self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet") @@ -95,9 +94,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys spending_tx = self.nodes[0].createrawtransaction([unspent_output], {getnewdestination()[2]: Decimal("49.998")}) spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output]) - # Check the signing completed successfully - assert 'complete' in spending_tx_signed - assert_equal(spending_tx_signed['complete'], True) + self.assert_signing_completed_successfully(spending_tx_signed) # Now test with P2PKH and P2PK scripts as the witnessScript for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent @@ -120,14 +117,19 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")}) spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}]) - # Check the signing completed successfully - assert 'complete' in spending_tx_signed - assert_equal(spending_tx_signed['complete'], True) + self.assert_signing_completed_successfully(spending_tx_signed) self.nodes[0].sendrawtransaction(spending_tx_signed['hex']) + def invalid_sighashtype_test(self): + self.log.info("Test signing transaction with invalid sighashtype") + tx = self.nodes[0].createrawtransaction(INPUTS, OUTPUTS) + privkeys = [self.nodes[0].get_deterministic_priv_key().key] + assert_raises_rpc_error(-8, "all is not a valid sighash parameter.", self.nodes[0].signrawtransactionwithkey, tx, privkeys, sighashtype="all") + def run_test(self): self.successful_signing_test() self.witness_script_test() + self.invalid_sighashtype_test() if __name__ == '__main__': diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 443cae86a1..78f58cf11f 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -689,6 +689,16 @@ def LegacySignatureHash(*args, **kwargs): else: return (hash256(msg), err) +def sign_input_legacy(tx, input_index, input_scriptpubkey, privkey, sighash_type=SIGHASH_ALL): + """Add legacy ECDSA signature for a given transaction input. Note that the signature + is prepended to the scriptSig field, i.e. additional data pushes necessary for more + complex spends than P2PK (e.g. pubkey for P2PKH) can be already set before.""" + (sighash, err) = LegacySignatureHash(input_scriptpubkey, tx, input_index, sighash_type) + assert err is None + der_sig = privkey.sign_ecdsa(sighash) + tx.vin[input_index].scriptSig = bytes(CScript([der_sig + bytes([sighash_type])])) + tx.vin[input_index].scriptSig + tx.rehash() + # TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided. # Performance optimization probably not necessary for python tests, however. # Note that this corresponds to sigversion == 1 in EvalScript, which is used diff --git a/test/functional/test_framework/siphash.py b/test/functional/test_framework/siphash.py index 884dbcab46..bd13b2c948 100644 --- a/test/functional/test_framework/siphash.py +++ b/test/functional/test_framework/siphash.py @@ -31,7 +31,7 @@ def siphash_round(v0, v1, v2, v3): def siphash(k0, k1, data): - assert type(data) == bytes + assert type(data) is bytes v0 = 0x736f6d6570736575 ^ k0 v1 = 0x646f72616e646f6d ^ k1 v2 = 0x6c7967656e657261 ^ k0 @@ -61,5 +61,5 @@ def siphash(k0, k1, data): def siphash256(k0, k1, num): - assert type(num) == int + assert type(num) is int return siphash(k0, k1, num.to_bytes(32, 'little')) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 5111d88e15..544a81602e 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -250,7 +250,7 @@ class TestNode(): # Wait for the node to finish reindex, block import, and # loading the mempool. Usually importing happens fast or # even "immediate" when the node is started. However, there - # is no guarantee and sometimes ThreadImport might finish + # is no guarantee and sometimes ImportBlocks might finish # later. This is going to cause intermittent test failures, # because generally the tests assume the node is fully # ready after being started. @@ -353,21 +353,13 @@ class TestNode(): for profile_name in tuple(self.perf_subprocesses.keys()): self._stop_perf(profile_name) - # Check that stderr is as expected - self.stderr.seek(0) - stderr = self.stderr.read().decode('utf-8').strip() - if stderr != expected_stderr: - raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) - - self.stdout.close() - self.stderr.close() - del self.p2ps[:] + assert (not expected_stderr) or wait_until_stopped # Must wait to check stderr if wait_until_stopped: - self.wait_until_stopped() + self.wait_until_stopped(expected_stderr=expected_stderr) - def is_node_stopped(self, expected_ret_code=0): + def is_node_stopped(self, *, expected_stderr="", expected_ret_code=0): """Checks whether the node has stopped. Returns True if the node has stopped. False otherwise. @@ -381,6 +373,15 @@ class TestNode(): # process has stopped. Assert that it didn't return an error code. assert return_code == expected_ret_code, self._node_msg( f"Node returned unexpected exit code ({return_code}) vs ({expected_ret_code}) when stopping") + # Check that stderr is as expected + self.stderr.seek(0) + stderr = self.stderr.read().decode('utf-8').strip() + if stderr != expected_stderr: + raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) + + self.stdout.close() + self.stderr.close() + self.running = False self.process = None self.rpc_connected = False @@ -388,9 +389,9 @@ class TestNode(): self.log.debug("Node stopped") return True - def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False): + def wait_until_stopped(self, *, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False, **kwargs): expected_ret_code = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS - wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code), timeout=timeout, timeout_factor=self.timeout_factor) + wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor) def replace_in_config(self, replacements): """ @@ -420,11 +421,15 @@ class TestNode(): return self.chain_path / 'debug.log' @property + def blocks_path(self) -> Path: + return self.chain_path / "blocks" + + @property def wallets_path(self) -> Path: return self.chain_path / "wallets" - def debug_log_bytes(self) -> int: - with open(self.debug_log_path, encoding='utf-8') as dl: + def debug_log_size(self, **kwargs) -> int: + with open(self.debug_log_path, **kwargs) as dl: dl.seek(0, 2) return dl.tell() @@ -433,13 +438,13 @@ class TestNode(): if unexpected_msgs is None: unexpected_msgs = [] time_end = time.time() + timeout * self.timeout_factor - prev_size = self.debug_log_bytes() + prev_size = self.debug_log_size(encoding="utf-8") # Must use same encoding that is used to read() below yield while True: found = True - with open(self.debug_log_path, encoding='utf-8') as dl: + with open(self.debug_log_path, encoding="utf-8", errors="replace") as dl: dl.seek(prev_size) log = dl.read() print_log = " - " + "\n - ".join(log.splitlines()) @@ -464,7 +469,7 @@ class TestNode(): the number of log lines we encountered when matching """ time_end = time.time() + timeout * self.timeout_factor - prev_size = self.debug_log_bytes() + prev_size = self.debug_log_size(mode="rb") # Must use same mode that is used to read() below yield @@ -643,10 +648,14 @@ class TestNode(): # in comparison to the upside of making tests less fragile and unexpected intermittent errors less likely. p2p_conn.sync_with_ping() - # Consistency check that the Bitcoin Core has received our user agent string. This checks the - # node's newest peer. It could be racy if another Bitcoin Core node has connected since we opened - # our connection, but we don't expect that to happen. - assert_equal(self.getpeerinfo()[-1]['subver'], P2P_SUBVERSION) + # Consistency check that the node received our user agent string. + # Find our connection in getpeerinfo by our address:port and theirs, as this combination is unique. + sockname = p2p_conn._transport.get_extra_info("socket").getsockname() + our_addr_and_port = f"{sockname[0]}:{sockname[1]}" + dst_addr_and_port = f"{p2p_conn.dstaddr}:{p2p_conn.dstport}" + info = [peer for peer in self.getpeerinfo() if peer["addr"] == our_addr_and_port and peer["addrbind"] == dst_addr_and_port] + assert_equal(len(info), 1) + assert_equal(info[0]["subver"], P2P_SUBVERSION) return p2p_conn diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 271095ea21..4d75194353 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -36,12 +36,11 @@ from test_framework.messages import ( ) from test_framework.script import ( CScript, - LegacySignatureHash, LEAF_VERSION_TAPSCRIPT, OP_NOP, OP_RETURN, OP_TRUE, - SIGHASH_ALL, + sign_input_legacy, taproot_construct, ) from test_framework.script_util import ( @@ -166,18 +165,16 @@ class MiniWallet: def sign_tx(self, tx, fixed_length=True): if self._mode == MiniWalletMode.RAW_P2PK: - (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) - assert err is None # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes) - # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes - der_sig = b'' - while not len(der_sig) == 71: - der_sig = self._priv_key.sign_ecdsa(sighash) + # with the DER header/skeleton data of 6 bytes added, plus 2 bytes scriptSig overhead + # (OP_PUSHn and SIGHASH_ALL), this leads to a scriptSig target size of 73 bytes + tx.vin[0].scriptSig = b'' + while not len(tx.vin[0].scriptSig) == 73: + tx.vin[0].scriptSig = b'' + sign_input_legacy(tx, 0, self._scriptPubKey, self._priv_key) if not fixed_length: break - tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) - tx.rehash() elif self._mode == MiniWalletMode.RAW_OP_TRUE: for i in tx.vin: i.scriptSig = CScript([OP_NOP] * 43) # pad to identical size diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a04ea4d978..d93e6fd6da 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -784,8 +784,8 @@ def check_script_prefixes(): def check_script_list(*, src_dir, fail_on_warn): """Check scripts directory. - Check that there are no scripts in the functional tests directory which are - not being run by pull-tester.py.""" + Check that all python files in this directory are categorized + as a test script or meta script.""" script_dir = src_dir + '/test/functional/' python_files = set([test_file for test_file in os.listdir(script_dir) if test_file.endswith(".py")]) missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 327dd43e5a..9d381a2cd2 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test bitcoin-wallet.""" -import hashlib import os import stat import subprocess @@ -13,9 +12,10 @@ import textwrap from collections import OrderedDict from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal - -BUFFER_SIZE = 16 * 1024 +from test_framework.util import ( + assert_equal, + sha256sum_file, +) class ToolWalletTest(BitcoinTestFramework): @@ -53,12 +53,7 @@ class ToolWalletTest(BitcoinTestFramework): assert_equal(p.poll(), 0) def wallet_shasum(self): - h = hashlib.sha1() - mv = memoryview(bytearray(BUFFER_SIZE)) - with open(self.wallet_path, 'rb', buffering=0) as f: - for n in iter(lambda: f.readinto(mv), 0): - h.update(mv[:n]) - return h.hexdigest() + return sha256sum_file(self.wallet_path).hex() def wallet_timestamp(self): return os.path.getmtime(self.wallet_path) diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py index 7d88e009c7..49e36b21c5 100755 --- a/test/functional/wallet_backwards_compatibility.py +++ b/test/functional/wallet_backwards_compatibility.py @@ -264,10 +264,11 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): os.path.join(node_master_wallets_dir, "u1_v16") ) load_res = node_master.loadwallet("u1_v16") - # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054 + # Make sure this wallet opens with only the migration warning. See https://github.com/bitcoin/bitcoin/pull/19054 if int(node_master.getnetworkinfo()["version"]) >= 249900: # loadwallet#warnings (added in v25) -- only present if there is a warning - assert "warnings" not in load_res + # Legacy wallets will have only a deprecation warning + assert_equal(load_res["warnings"], ["Wallet loaded successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future. Legacy wallets can be migrated to a descriptor wallet with migratewallet."]) else: # loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning assert_equal(load_res["warning"], '') diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index a1b805c09e..a4eb6ee29a 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -328,7 +328,7 @@ class WalletTest(BitcoinTestFramework): for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]: assert_raises_rpc_error(-3, msg, self.nodes[2].sendmany, amounts={address: 1.0}, fee_rate=invalid_value) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: assert_raises_rpc_error(-3, msg, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=invalid_value) # Test fee_rate out of range (negative number). assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=-1) @@ -523,7 +523,7 @@ class WalletTest(BitcoinTestFramework): for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]: assert_raises_rpc_error(-3, msg, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=invalid_value) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: assert_raises_rpc_error(-3, msg, self.nodes[2].sendtoaddress, address=address, amount=10, fee_rate=invalid_value) # Test fee_rate out of range (negative number). assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=-1) diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index b9ebf64c22..e69c1829ca 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -24,9 +24,11 @@ from test_framework.messages import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_fee_amount, assert_greater_than, assert_raises_rpc_error, get_fee, + find_vout_for_address, ) from test_framework.wallet import MiniWallet @@ -109,6 +111,8 @@ class BumpFeeTest(BitcoinTestFramework): test_small_output_with_feerate_succeeds(self, rbf_node, dest_address) test_no_more_inputs_fails(self, rbf_node, dest_address) self.test_bump_back_to_yourself() + self.test_provided_change_pos(rbf_node) + self.test_single_output() # Context independent tests test_feerate_checks_replaced_outputs(self, rbf_node, peer_node) @@ -137,7 +141,7 @@ class BumpFeeTest(BitcoinTestFramework): for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]: assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value) # Test fee_rate out of range (negative number). assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, fee_rate=-1) @@ -174,6 +178,13 @@ class BumpFeeTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]}) + self.log.info("Test reduce_output option") + assert_raises_rpc_error(-1, "JSON integer out of range", rbf_node.bumpfee, rbfid, {"reduce_output": -1}) + assert_raises_rpc_error(-8, "Change position is out of range", rbf_node.bumpfee, rbfid, {"reduce_output": 2}) + + self.log.info("Test outputs and reduce_output cannot both be provided") + assert_raises_rpc_error(-8, "Cannot specify both new outputs to use and an output index to reduce", rbf_node.bumpfee, rbfid, {"reduce_output": 2, "outputs": [{dest_address: 0.1}]}) + self.clear_mempool() def test_bump_back_to_yourself(self): @@ -225,6 +236,72 @@ class BumpFeeTest(BitcoinTestFramework): node.unloadwallet("back_to_yourself") + def test_provided_change_pos(self, rbf_node): + self.log.info("Test the reduce_output option") + + change_addr = rbf_node.getnewaddress() + dest_addr = rbf_node.getnewaddress() + assert_equal(rbf_node.getaddressinfo(change_addr)["ischange"], False) + assert_equal(rbf_node.getaddressinfo(dest_addr)["ischange"], False) + + send_res = rbf_node.send(outputs=[{dest_addr: 1}], options={"change_address": change_addr}) + assert send_res["complete"] + txid = send_res["txid"] + + tx = rbf_node.gettransaction(txid=txid, verbose=True) + assert_equal(len(tx["decoded"]["vout"]), 2) + + change_pos = find_vout_for_address(rbf_node, txid, change_addr) + change_value = tx["decoded"]["vout"][change_pos]["value"] + + bumped = rbf_node.bumpfee(txid, {"reduce_output": change_pos}) + new_txid = bumped["txid"] + + new_tx = rbf_node.gettransaction(txid=new_txid, verbose=True) + assert_equal(len(new_tx["decoded"]["vout"]), 2) + new_change_pos = find_vout_for_address(rbf_node, new_txid, change_addr) + new_change_value = new_tx["decoded"]["vout"][new_change_pos]["value"] + + assert_greater_than(change_value, new_change_value) + + + def test_single_output(self): + self.log.info("Test that single output txs can be bumped") + node = self.nodes[1] + + node.createwallet("single_out_rbf") + wallet = node.get_wallet_rpc("single_out_rbf") + + addr = wallet.getnewaddress() + amount = Decimal("0.001") + # Make 2 UTXOs + self.nodes[0].sendtoaddress(addr, amount) + self.nodes[0].sendtoaddress(addr, amount) + self.generate(self.nodes[0], 1) + utxos = wallet.listunspent() + + tx = wallet.sendall(recipients=[wallet.getnewaddress()], fee_rate=2, options={"inputs": [utxos[0]]}) + + # Reduce the only output with a crazy high feerate, should fail as the output would be dust + assert_raises_rpc_error(-4, "The transaction amount is too small to pay the fee", wallet.bumpfee, txid=tx["txid"], options={"fee_rate": 1100, "reduce_output": 0}) + + # Reduce the only output successfully + bumped = wallet.bumpfee(txid=tx["txid"], options={"fee_rate": 10, "reduce_output": 0}) + bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True) + assert_equal(len(bumped_tx["decoded"]["vout"]), 1) + assert_equal(len(bumped_tx["decoded"]["vin"]), 1) + assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped["fee"], amount) + assert_fee_amount(bumped["fee"], bumped_tx["decoded"]["vsize"], Decimal(10) / Decimal(1e8) * 1000) + + # Bumping without reducing adds a new input and output + bumped = wallet.bumpfee(txid=bumped["txid"], options={"fee_rate": 20}) + bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True) + assert_equal(len(bumped_tx["decoded"]["vout"]), 2) + assert_equal(len(bumped_tx["decoded"]["vin"]), 2) + assert_fee_amount(bumped["fee"], bumped_tx["decoded"]["vsize"], Decimal(20) / Decimal(1e8) * 1000) + + wallet.unloadwallet() + def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): self.log.info('Test simple bumpfee: {}'.format(mode)) rbfid = spend_one_input(rbf_node, dest_address) diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 46706d6ad2..e367daae2c 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -183,7 +183,6 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 1.0 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test that we have enough inputs @@ -193,8 +192,6 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 2.2 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) - rawtxfund = self.nodes[2].fundrawtransaction(rawtx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test if we have enough inputs @@ -206,13 +203,9 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ ] outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 - for out in dec_tx['vout']: - totalOut += out['value'] assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') @@ -335,10 +328,8 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 matchingOuts = 0 for i, out in enumerate(dec_tx['vout']): - totalOut += out['value'] if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 else: @@ -364,12 +355,9 @@ class RawTransactionsTest(BitcoinTestFramework): # Should fail without add_inputs: assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, self.nodes[2].fundrawtransaction, rawtx, add_inputs=False) rawtxfund = self.nodes[2].fundrawtransaction(rawtx, add_inputs=True) - dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 matchingOuts = 0 for out in dec_tx['vout']: - totalOut += out['value'] if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 @@ -400,10 +388,8 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx, add_inputs=True) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 matchingOuts = 0 for out in dec_tx['vout']: - totalOut += out['value'] if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 @@ -581,11 +567,20 @@ class RawTransactionsTest(BitcoinTestFramework): def test_locked_wallet(self): self.log.info("Test fundrawtxn with locked wallet and hardened derivation") - self.nodes[1].encryptwallet("test") + df_wallet = self.nodes[1].get_wallet_rpc(self.default_wallet_name) + self.nodes[1].createwallet(wallet_name="locked_wallet", descriptors=self.options.descriptors) + wallet = self.nodes[1].get_wallet_rpc("locked_wallet") + + # Add some balance to the wallet (this will be reverted at the end of the test) + df_wallet.sendall(recipients=[wallet.getnewaddress()]) + self.generate(self.nodes[1], 1) + + # Encrypt wallet and import descriptors + wallet.encryptwallet("test") if self.options.descriptors: - self.nodes[1].walletpassphrase('test', 10) - self.nodes[1].importdescriptors([{ + wallet.walletpassphrase('test', 10) + wallet.importdescriptors([{ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), 'timestamp': 'now', 'active': True @@ -596,49 +591,57 @@ class RawTransactionsTest(BitcoinTestFramework): 'active': True, 'internal': True }]) - self.nodes[1].walletlock() + wallet.walletlock() # Drain the keypool. - self.nodes[1].getnewaddress() - self.nodes[1].getrawchangeaddress() + wallet.getnewaddress() + wallet.getrawchangeaddress() - # Choose 2 inputs - inputs = self.nodes[1].listunspent()[0:2] - value = sum(inp["amount"] for inp in inputs) - Decimal("0.00000500") # Pay a 500 sat fee + # Choose input + inputs = wallet.listunspent() + # Deduce fee to produce a changeless transaction + value = inputs[0]["amount"] - Decimal("0.00002200") outputs = {self.nodes[0].getnewaddress():value} - rawtx = self.nodes[1].createrawtransaction(inputs, outputs) + rawtx = wallet.createrawtransaction(inputs, outputs) # fund a transaction that does not require a new key for the change output - self.nodes[1].fundrawtransaction(rawtx) + funded_tx = wallet.fundrawtransaction(rawtx) + assert_equal(funded_tx["changepos"], -1) # fund a transaction that requires a new key for the change output # creating the key must be impossible because the wallet is locked outputs = {self.nodes[0].getnewaddress():value - Decimal("0.1")} - rawtx = self.nodes[1].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx) + rawtx = wallet.createrawtransaction(inputs, outputs) + assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", wallet.fundrawtransaction, rawtx) # Refill the keypool. - self.nodes[1].walletpassphrase("test", 100) - self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address - self.nodes[1].walletlock() + wallet.walletpassphrase("test", 100) + wallet.keypoolrefill(8) #need to refill the keypool to get an internal change address + wallet.walletlock() - assert_raises_rpc_error(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2) + assert_raises_rpc_error(-13, "walletpassphrase", wallet.sendtoaddress, self.nodes[0].getnewaddress(), 1.2) oldBalance = self.nodes[0].getbalance() inputs = [] outputs = {self.nodes[0].getnewaddress():1.1} - rawtx = self.nodes[1].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[1].fundrawtransaction(rawtx) + rawtx = wallet.createrawtransaction(inputs, outputs) + fundedTx = wallet.fundrawtransaction(rawtx) + assert fundedTx["changepos"] != -1 # Now we need to unlock. - self.nodes[1].walletpassphrase("test", 600) - signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) - self.nodes[1].sendrawtransaction(signedTx['hex']) + wallet.walletpassphrase("test", 600) + signedTx = wallet.signrawtransactionwithwallet(fundedTx['hex']) + wallet.sendrawtransaction(signedTx['hex']) self.generate(self.nodes[1], 1) # Make sure funds are received at node1. assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) + # Restore pre-test wallet state + wallet.sendall(recipients=[df_wallet.getnewaddress(), df_wallet.getnewaddress(), df_wallet.getnewaddress()]) + wallet.unloadwallet() + self.generate(self.nodes[1], 1) + def test_many_inputs_fee(self): """Multiple (~19) inputs tx test | Compare fee.""" self.log.info("Test fundrawtxn fee with many inputs") @@ -829,7 +832,7 @@ class RawTransactionsTest(BitcoinTestFramework): for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]: assert_raises_rpc_error(-3, "Invalid amount", node.fundrawtransaction, rawtx, add_inputs=True, **{param: invalid_value}) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: assert_raises_rpc_error(-3, "Invalid amount", node.fundrawtransaction, rawtx, fee_rate=invalid_value, add_inputs=True) diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index b64e5a8cd8..925376e8cd 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -8,6 +8,8 @@ import random import shutil from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import COIN, CTransaction, CTxOut +from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_script, script_to_p2wsh_script from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -67,6 +69,15 @@ class WalletMigrationTest(BitcoinTestFramework): del d["parent_descs"] assert_equal(received_list_txs, expected_list_txs) + def check_address(self, wallet, addr, is_mine, is_change, label): + addr_info = wallet.getaddressinfo(addr) + assert_equal(addr_info['ismine'], is_mine) + assert_equal(addr_info['ischange'], is_change) + if label is not None: + assert_equal(addr_info['labels'], [label]), + else: + assert_equal(addr_info['labels'], []), + def test_basic(self): default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) @@ -504,6 +515,179 @@ class WalletMigrationTest(BitcoinTestFramework): assert wallet_path.is_dir() assert wallet_dat_path.is_file() + def test_addressbook(self): + df_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + self.log.info("Test migration of address book data") + wallet = self.create_legacy_wallet("legacy_addrbook") + df_wallet.sendtoaddress(wallet.getnewaddress(), 3) + + # Import watch-only script to create a watch-only wallet after migration + watch_addr = df_wallet.getnewaddress() + wallet.importaddress(watch_addr) + df_wallet.sendtoaddress(watch_addr, 2) + + # Import solvable script + multi_addr1 = wallet.getnewaddress() + multi_addr2 = wallet.getnewaddress() + multi_addr3 = df_wallet.getnewaddress() + wallet.importpubkey(df_wallet.getaddressinfo(multi_addr3)["pubkey"]) + ms_addr_info = wallet.addmultisigaddress(2, [multi_addr1, multi_addr2, multi_addr3]) + + self.generate(self.nodes[0], 1) + + # Test vectors + addr_external = { + "addr": df_wallet.getnewaddress(), + "is_mine": False, + "is_change": False, + "label": "" + } + addr_external_with_label = { + "addr": df_wallet.getnewaddress(), + "is_mine": False, + "is_change": False, + "label": "external" + } + addr_internal = { + "addr": wallet.getnewaddress(), + "is_mine": True, + "is_change": False, + "label": "" + } + addr_internal_with_label = { + "addr": wallet.getnewaddress(), + "is_mine": True, + "is_change": False, + "label": "internal" + } + change_address = { + "addr": wallet.getrawchangeaddress(), + "is_mine": True, + "is_change": True, + "label": None + } + watch_only_addr = { + "addr": watch_addr, + "is_mine": False, + "is_change": False, + "label": "imported" + } + ms_addr = { + "addr": ms_addr_info['address'], + "is_mine": False, + "is_change": False, + "label": "multisig" + } + + # To store the change address in the addressbook need to send coins to it + wallet.send(outputs=[{wallet.getnewaddress(): 2}], options={"change_address": change_address['addr']}) + self.generate(self.nodes[0], 1) + + # Util wrapper func for 'addr_info' + def check(info, node): + self.check_address(node, info['addr'], info['is_mine'], info['is_change'], info["label"]) + + # Pre-migration: set label and perform initial checks + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, watch_only_addr, ms_addr]: + if not addr_info['is_change']: + wallet.setlabel(addr_info['addr'], addr_info["label"]) + check(addr_info, wallet) + + # Migrate wallet + info_migration = wallet.migratewallet() + wallet_wo = self.nodes[0].get_wallet_rpc(info_migration["watchonly_name"]) + wallet_solvables = self.nodes[0].get_wallet_rpc(info_migration["solvables_name"]) + + ######################### + # Post migration checks # + ######################### + + # First check the main wallet + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]: + check(addr_info, wallet) + + # Watch-only wallet will contain the watch-only entry (with 'is_mine=True') and all external addresses ('send') + self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"]) + for addr_info in [addr_external, addr_external_with_label, ms_addr]: + check(addr_info, wallet_wo) + + # Solvables wallet will contain the multisig entry (with 'is_mine=True') and all external addresses ('send') + self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"]) + for addr_info in [addr_external, addr_external_with_label]: + check(addr_info, wallet_solvables) + + ######################################################################################## + # Now restart migrated wallets and verify that the addressbook entries are still there # + ######################################################################################## + + # First the main wallet + self.nodes[0].unloadwallet("legacy_addrbook") + self.nodes[0].loadwallet("legacy_addrbook") + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]: + check(addr_info, wallet) + + # Watch-only wallet + self.nodes[0].unloadwallet(info_migration["watchonly_name"]) + self.nodes[0].loadwallet(info_migration["watchonly_name"]) + self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"]) + for addr_info in [addr_external, addr_external_with_label, ms_addr]: + check(addr_info, wallet_wo) + + # Solvables wallet + self.nodes[0].unloadwallet(info_migration["solvables_name"]) + self.nodes[0].loadwallet(info_migration["solvables_name"]) + self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"]) + for addr_info in [addr_external, addr_external_with_label]: + check(addr_info, wallet_solvables) + + def test_migrate_raw_p2sh(self): + self.log.info("Test migration of watch-only raw p2sh script") + df_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + wallet = self.create_legacy_wallet("raw_p2sh") + + def send_to_script(script, amount): + tx = CTransaction() + tx.vout.append(CTxOut(nValue=amount*COIN, scriptPubKey=script)) + + hex_tx = df_wallet.fundrawtransaction(tx.serialize().hex())['hex'] + signed_tx = df_wallet.signrawtransactionwithwallet(hex_tx) + df_wallet.sendrawtransaction(signed_tx['hex']) + self.generate(self.nodes[0], 1) + + # Craft sh(pkh(key)) script and send coins to it + pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"] + script_pkh = key_to_p2pkh_script(pubkey) + script_sh_pkh = script_to_p2sh_script(script_pkh) + send_to_script(script=script_sh_pkh, amount=2) + + # Import script and check balance + wallet.rpc.importaddress(address=script_pkh.hex(), label="raw_spk", rescan=True, p2sh=True) + assert_equal(wallet.getbalances()['watchonly']['trusted'], 2) + + # Craft wsh(pkh(key)) and send coins to it + pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"] + script_wsh_pkh = script_to_p2wsh_script(key_to_p2pkh_script(pubkey)) + send_to_script(script=script_wsh_pkh, amount=3) + + # Import script and check balance + wallet.rpc.importaddress(address=script_wsh_pkh.hex(), label="raw_spk2", rescan=True, p2sh=False) + assert_equal(wallet.getbalances()['watchonly']['trusted'], 5) + + # Migrate wallet and re-check balance + info_migration = wallet.migratewallet() + wallet_wo = self.nodes[0].get_wallet_rpc(info_migration["watchonly_name"]) + + # Watch-only balance is under "mine". + assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5) + # The watch-only scripts are no longer part of the main wallet + assert_equal(wallet.getbalances()['mine']['trusted'], 0) + + # Just in case, also verify wallet restart + self.nodes[0].unloadwallet(info_migration["watchonly_name"]) + self.nodes[0].loadwallet(info_migration["watchonly_name"]) + assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5) + def run_test(self): self.generate(self.nodes[0], 101) @@ -518,6 +702,8 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_unloaded_by_path() self.test_default_wallet() self.test_direct_file() + self.test_addressbook() + self.test_migrate_raw_p2sh() if __name__ == '__main__': WalletMigrationTest().main() diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py index 7bc3424bf4..45f0df1c76 100755 --- a/test/functional/wallet_miniscript.py +++ b/test/functional/wallet_miniscript.py @@ -277,6 +277,18 @@ class WalletMiniscriptTest(BitcoinTestFramework): assert not res["success"] assert "is not sane: witnesses without signature exist" in res["error"]["message"] + # Sanity check we wouldn't let an unspendable Miniscript descriptor in + res = self.ms_wo_wallet.importdescriptors( + [ + { + "desc": descsum_create("wsh(0)"), + "active": False, + "timestamp": "now", + } + ] + )[0] + assert not res["success"] and "is not satisfiable" in res["error"]["message"] + # Test we can track any type of Miniscript for ms in MINISCRIPTS: self.watchonly_test(ms) diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 7bdb6f5e3a..f36d8efda7 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -108,9 +108,13 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): # Set correct m_best_block_time, which is used in ResubmitWalletTransactions node.syncwithvalidationinterfacequeue() - # Evict these txs from the mempool evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5 - node.setmocktime(evict_time) + # Flush out currently scheduled resubmit attempt now so that there can't be one right between eviction and check. + with node.assert_debug_log(['resubmit 2 unconfirmed transactions']): + node.setmocktime(evict_time) + node.mockscheduler(60) + + # Evict these txs from the mempool indep_send = node.send(outputs=[{node.getnewaddress(): 1}], inputs=[indep_utxo]) node.getmempoolentry(indep_send["txid"]) assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid) diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index d7bb6ab1e7..4728f53be7 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -387,7 +387,7 @@ class WalletSendTest(BitcoinTestFramework): self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg)) self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg)) # Test fee_rate values that cannot be represented in sat/vB. - for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]: + for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]: self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg)) self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg)) # Test fee_rate out of range (negative number). diff --git a/test/functional/wallet_signrawtransactionwithwallet.py b/test/functional/wallet_signrawtransactionwithwallet.py index 3d2f41cb83..d560dfdc11 100755 --- a/test/functional/wallet_signrawtransactionwithwallet.py +++ b/test/functional/wallet_signrawtransactionwithwallet.py @@ -33,6 +33,10 @@ from decimal import ( getcontext, ) + +RAW_TX = '020000000156b958f78e3f24e0b2f4e4db1255426b0902027cb37e3ddadb52e37c3557dddb0000000000ffffffff01c0a6b929010000001600149a2ee8c77140a053f36018ac8124a6ececc1668a00000000' + + class SignRawTransactionWithWalletTest(BitcoinTestFramework): def add_options(self, parser): self.add_wallet_options(parser) @@ -47,10 +51,12 @@ class SignRawTransactionWithWalletTest(BitcoinTestFramework): def test_with_lock_outputs(self): self.log.info("Test correct error reporting when trying to sign a locked output") self.nodes[0].encryptwallet("password") + assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signrawtransactionwithwallet, RAW_TX) + self.nodes[0].walletpassphrase("password", 9999) - rawTx = '020000000156b958f78e3f24e0b2f4e4db1255426b0902027cb37e3ddadb52e37c3557dddb0000000000ffffffff01c0a6b929010000001600149a2ee8c77140a053f36018ac8124a6ececc1668a00000000' - - assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signrawtransactionwithwallet, rawTx) + def test_with_invalid_sighashtype(self): + self.log.info("Test signrawtransactionwithwallet raises if an invalid sighashtype is passed") + assert_raises_rpc_error(-8, "all is not a valid sighash parameter.", self.nodes[0].signrawtransactionwithwallet, hexstring=RAW_TX, sighashtype="all") def script_verification_error_test(self): """Create and sign a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script. @@ -299,6 +305,7 @@ class SignRawTransactionWithWalletTest(BitcoinTestFramework): self.script_verification_error_test() self.OP_1NEGATE_test() self.test_with_lock_outputs() + self.test_with_invalid_sighashtype() self.test_fully_signed_tx() self.test_signing_with_csv() self.test_signing_with_cltv() diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index 84028f3dc5..c9975af225 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -20,8 +20,7 @@ def get_fuzz_env(*, target, source_dir): 'FUZZ': target, 'UBSAN_OPTIONS': f'suppressions={source_dir}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1', - 'ASAN_OPTIONS': # symbolizer disabled due to https://github.com/google/sanitizers/issues/1364#issuecomment-761072085 - 'symbolize=0:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1', + "ASAN_OPTIONS": "detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1", } @@ -193,6 +192,42 @@ def main(): ) +def transform_process_message_target(targets, src_dir): + """Add a target per process message, and also keep ("process_message", {}) to allow for + cross-pollination, or unlimited search""" + + p2p_msg_target = "process_message" + if (p2p_msg_target, {}) in targets: + lines = subprocess.run( + ["git", "grep", "--function-context", "g_all_net_message_types{", src_dir / "src" / "protocol.cpp"], + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout.splitlines() + lines = [l.split("::", 1)[1].split(",")[0].lower() for l in lines if l.startswith("src/protocol.cpp- NetMsgType::")] + assert len(lines) + targets += [(p2p_msg_target, {"LIMIT_TO_MESSAGE_TYPE": m}) for m in lines] + return targets + + +def transform_rpc_target(targets, src_dir): + """Add a target per RPC command, and also keep ("rpc", {}) to allow for cross-pollination, + or unlimited search""" + + rpc_target = "rpc" + if (rpc_target, {}) in targets: + lines = subprocess.run( + ["git", "grep", "--function-context", "RPC_COMMANDS_SAFE_FOR_FUZZING{", src_dir / "src" / "test" / "fuzz" / "rpc.cpp"], + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout.splitlines() + lines = [l.split("\"", 1)[1].split("\"")[0] for l in lines if l.startswith("src/test/fuzz/rpc.cpp- \"")] + assert len(lines) + targets += [(rpc_target, {"LIMIT_TO_RPC_COMMAND": r}) for r in lines] + return targets + + def generate_corpus(*, fuzz_pool, src_dir, build_dir, corpus_dir, targets): """Generates new corpus. @@ -200,29 +235,36 @@ def generate_corpus(*, fuzz_pool, src_dir, build_dir, corpus_dir, targets): {corpus_dir}. """ logging.info("Generating corpus to {}".format(corpus_dir)) + targets = [(t, {}) for t in targets] # expand to add dictionary for target-specific env variables + targets = transform_process_message_target(targets, Path(src_dir)) + targets = transform_rpc_target(targets, Path(src_dir)) - def job(command, t): - logging.debug("Running '{}'\n".format(" ".join(command))) + def job(command, t, t_env): + logging.debug(f"Running '{command}'") logging.debug("Command '{}' output:\n'{}'\n".format( - ' '.join(command), + command, subprocess.run( command, - env=get_fuzz_env(target=t, source_dir=src_dir), + env={ + **t_env, + **get_fuzz_env(target=t, source_dir=src_dir), + }, check=True, stderr=subprocess.PIPE, text=True, - ).stderr)) + ).stderr, + )) futures = [] - for target in targets: - target_corpus_dir = os.path.join(corpus_dir, target) + for target, t_env in targets: + target_corpus_dir = corpus_dir / target os.makedirs(target_corpus_dir, exist_ok=True) command = [ os.path.join(build_dir, 'src', 'test', 'fuzz', 'fuzz'), "-runs=100000", target_corpus_dir, ] - futures.append(fuzz_pool.submit(job, command, target)) + futures.append(fuzz_pool.submit(job, command, target, t_env)) for future in as_completed(futures): future.result() diff --git a/test/lint/README.md b/test/lint/README.md index 704922d7ab..d9cfeb50ed 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -7,10 +7,8 @@ To run linters locally with the same versions as the CI environment, use the inc Dockerfile: ```sh -cd ./ci/lint -docker build -t bitcoin-linter . +DOCKER_BUILDKIT=1 docker build -t bitcoin-linter --file "./ci/lint_imagefile" ./ -cd /root/of/bitcoin/repo docker run --rm -v $(pwd):/bitcoin -it bitcoin-linter ``` diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index f54126e023..43addab2f3 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -16,7 +16,7 @@ import re import sys FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [ - 'FatalError,0', + 'FatalErrorf,0', 'fprintf,1', 'tfm::format,1', # Assuming tfm::::format(std::ostream&, ... 'LogConnectFailure,1', diff --git a/test/lint/lint-python-utf8-encoding.py b/test/lint/lint-python-utf8-encoding.py index 64d04bff57..8c9266470f 100755 --- a/test/lint/lint-python-utf8-encoding.py +++ b/test/lint/lint-python-utf8-encoding.py @@ -28,7 +28,7 @@ def check_fileopens(): if e.returncode > 1: raise e - filtered_fileopens = [fileopen for fileopen in fileopens if not re.search(r"encoding=.(ascii|utf8|utf-8).|open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]", fileopen)] + filtered_fileopens = [fileopen for fileopen in fileopens if not re.search(r"encoding=.(ascii|utf8|utf-8).|open\([^,]*, (\*\*kwargs|['\"][^'\"]*b[^'\"]*['\"])", fileopen)] return filtered_fileopens diff --git a/test/lint/run-lint-format-strings.py b/test/lint/run-lint-format-strings.py index d1896dba84..ed98b1b2f8 100755 --- a/test/lint/run-lint-format-strings.py +++ b/test/lint/run-lint-format-strings.py @@ -14,7 +14,8 @@ import sys FALSE_POSITIVES = [ ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), - ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), + ("src/index/base.cpp", "FatalErrorf(const char* fmt, const Args&... args)"), + ("src/index/base.h", "FatalErrorf(const char* fmt, const Args&... args)"), ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), ("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), ("src/test/translation_tests.cpp", "strprintf(format, arg)"), diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 74703b04ec..533e2eae51 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -1,9 +1,7 @@ +# Suppressions should use `sanitize-type:ClassName::MethodName`. + # -fsanitize=undefined suppressions # ================================= -# The suppressions would be `sanitize-type:ClassName::MethodName`, -# however due to a bug in clang the symbolizer is disabled and thus no symbol -# names can be used. -# See https://github.com/google/sanitizers/issues/1364 # -fsanitize=integer suppressions # =============================== @@ -11,26 +9,28 @@ # ------------ # Suppressions in dependencies that are developed outside this repository. unsigned-integer-overflow:*/include/c++/ -# unsigned-integer-overflow in FuzzedDataProvider's ConsumeIntegralInRange -unsigned-integer-overflow:FuzzedDataProvider.h +unsigned-integer-overflow:FuzzedDataProvider::ConsumeIntegralInRange unsigned-integer-overflow:leveldb/ unsigned-integer-overflow:minisketch/ +unsigned-integer-overflow:secp256k1/ unsigned-integer-overflow:test/fuzz/crypto_diff_fuzz_chacha20.cpp implicit-integer-sign-change:*/include/boost/ implicit-integer-sign-change:*/include/c++/ implicit-integer-sign-change:*/new_allocator.h implicit-integer-sign-change:crc32c/ -# implicit-integer-sign-change in FuzzedDataProvider's ConsumeIntegralInRange -implicit-integer-sign-change:FuzzedDataProvider.h implicit-integer-sign-change:minisketch/ +implicit-integer-sign-change:secp256k1/ implicit-signed-integer-truncation:*/include/c++/ implicit-signed-integer-truncation:leveldb/ +implicit-signed-integer-truncation:secp256k1/ implicit-unsigned-integer-truncation:*/include/c++/ implicit-unsigned-integer-truncation:leveldb/ +implicit-unsigned-integer-truncation:secp256k1/ implicit-unsigned-integer-truncation:test/fuzz/crypto_diff_fuzz_chacha20.cpp shift-base:*/include/c++/ shift-base:leveldb/ shift-base:minisketch/ +shift-base:secp256k1/ shift-base:test/fuzz/crypto_diff_fuzz_chacha20.cpp # Unsigned integer overflow occurs when the result of an unsigned integer # computation cannot be represented in its type. Unlike signed integer overflow, |