diff options
Diffstat (limited to 'test/functional')
60 files changed, 1100 insertions, 463 deletions
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py index 9cff8042a8..ae483fe449 100755 --- a/test/functional/feature_asmap.py +++ b/test/functional/feature_asmap.py @@ -111,6 +111,14 @@ class AsmapTest(BitcoinTestFramework): self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg) os.remove(self.default_asmap) + def test_asmap_health_check(self): + self.log.info('Test bitcoind -asmap logs ASMap Health Check with basic stats') + shutil.copyfile(self.asmap_raw, self.default_asmap) + msg = "ASMap Health Check: 2 clearnet peers are mapped to 1 ASNs with 0 peers being unmapped" + with self.node.assert_debug_log(expected_msgs=[msg]): + self.start_node(0, extra_args=['-asmap']) + os.remove(self.default_asmap) + def run_test(self): self.node = self.nodes[0] self.datadir = self.node.chain_path @@ -124,6 +132,7 @@ class AsmapTest(BitcoinTestFramework): self.test_asmap_interaction_with_addrman_containing_entries() self.test_default_asmap_with_missing_file() self.test_empty_asmap() + self.test_asmap_health_check() if __name__ == '__main__': diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index 9c265649d5..2e3589b020 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -33,6 +33,8 @@ Interesting starting states could be loading a snapshot when the current chain t - TODO: Not an ancestor or a descendant of the snapshot block and has more work """ +from shutil import rmtree + from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -73,7 +75,7 @@ class AssumeutxoTest(BitcoinTestFramework): with self.nodes[1].assert_debug_log([log_msg]): assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", self.nodes[1].loadtxoutset, bad_snapshot_path) - self.log.info(" - snapshot file refering to a block that is not in the assumeutxo parameters") + self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters") prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1) bogus_block_hash = "0" * 64 # Represents any unknown block hash for bad_block_hash in [bogus_block_hash, prev_block_hash]: @@ -107,6 +109,28 @@ class AssumeutxoTest(BitcoinTestFramework): f.write(valid_snapshot_contents[(32 + 8 + offset + len(content)):]) expected_error(log_msg=f"[snapshot] bad snapshot content hash: expected 61d9c2b29a2571a5fe285fe2d8554f91f93309666fc9b8223ee96338de25ff53, got {wrong_hash}") + def test_invalid_chainstate_scenarios(self): + self.log.info("Test different scenarios of invalid snapshot chainstate in datadir") + + self.log.info(" - snapshot chainstate referring to a block that is not in the assumeutxo parameters") + self.stop_node(0) + chainstate_snapshot_path = self.nodes[0].chain_path / "chainstate_snapshot" + chainstate_snapshot_path.mkdir() + with open(chainstate_snapshot_path / "base_blockhash", 'wb') as f: + f.write(b'z' * 32) + + def expected_error(log_msg="", error_msg=""): + with self.nodes[0].assert_debug_log([log_msg]): + self.nodes[0].assert_start_raises_init_error(expected_msg=error_msg) + + expected_error_msg = f"Error: A fatal internal error occurred, see debug.log for details" + error_details = f"Assumeutxo data not found for the given blockhash" + expected_error(log_msg=error_details, error_msg=expected_error_msg) + + # resurrect node again + rmtree(chainstate_snapshot_path) + self.start_node(0) + def run_test(self): """ Bring up two (disconnected) nodes, mine some new blocks on the first, @@ -166,6 +190,7 @@ class AssumeutxoTest(BitcoinTestFramework): assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT) self.test_invalid_snapshot_scenarios(dump_output['path']) + self.test_invalid_chainstate_scenarios() self.log.info(f"Loading snapshot into second node from {dump_output['path']}") loaded = n1.loadtxoutset(dump_output['path']) diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index 24a68a04bf..567207915e 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -30,6 +30,12 @@ class FilelockTest(BitcoinTestFramework): expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['PACKAGE_NAME']} is probably already running." self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir_path}', '-noserver'], expected_msg=expected_msg) + self.log.info("Check that cookie and PID file are not deleted when attempting to start a second bitcoind using the same datadir") + cookie_file = datadir / ".cookie" + assert cookie_file.exists() # should not be deleted during the second bitcoind instance shutdown + pid_file = datadir / "bitcoind.pid" + assert pid_file.exists() + if self.is_wallet_compiled(): def check_wallet_filelock(descriptors): wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)]) diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 94f5116f9b..142d75a851 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2021-2022 The Bitcoin Core developers +# Copyright (c) 2021-present The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Stress tests related to node initialization.""" @@ -133,15 +133,12 @@ class InitStressTest(BitcoinTestFramework): for target_file in target_files: self.log.info(f"Perturbing file to ensure failure {target_file}") - with open(target_file, "rb") as tf_read: - contents = tf_read.read() - tweaked_contents = bytearray(contents) + with open(target_file, "r+b") as tf: # Since the genesis block is not checked by -checkblocks, the # perturbation window must be chosen such that a higher block # in blk*.dat is affected. - tweaked_contents[150:350] = b'1' * 200 - with open(target_file, "wb") as tf_write: - tf_write.write(bytes(tweaked_contents)) + tf.seek(150) + tf.write(b"1" * 200) start_expecting_error(err_fragment) diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 6c467fa613..4dc19222c4 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -88,14 +88,11 @@ class SegWitTest(BitcoinTestFramework): self.extra_args = [ [ "-acceptnonstdtxn=1", - "-rpcserialversion=0", - "-deprecatedrpc=serialversion", "-testactivationheight=segwit@165", "-addresstype=legacy", ], [ "-acceptnonstdtxn=1", - "-rpcserialversion=1", "-testactivationheight=segwit@165", "-addresstype=legacy", ], @@ -224,18 +221,6 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Witness program hash mismatch)", p2sh_ids[NODE_0][P2WPKH][0], sign=False, redeem_script=witness_script(False, self.pubkey[0])) self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Witness program was passed an empty witness)", p2sh_ids[NODE_0][P2WSH][0], sign=False, redeem_script=witness_script(True, self.pubkey[0])) - self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag") - assert self.nodes[2].getblock(blockhash, False) != self.nodes[0].getblock(blockhash, False) - assert self.nodes[1].getblock(blockhash, False) == self.nodes[2].getblock(blockhash, False) - - for tx_id in segwit_tx_list: - tx = tx_from_hex(self.nodes[2].gettransaction(tx_id)["hex"]) - assert self.nodes[2].getrawtransaction(tx_id, False, blockhash) != self.nodes[0].getrawtransaction(tx_id, False, blockhash) - assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].getrawtransaction(tx_id, False, blockhash) - assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) != self.nodes[2].gettransaction(tx_id)["hex"] - assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].gettransaction(tx_id)["hex"] - assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) == tx.serialize_without_witness().hex() - # Coinbase contains the witness commitment nonce, check that RPC shows us coinbase_txid = self.nodes[2].getblock(blockhash)['tx'][0] coinbase_tx = self.nodes[2].gettransaction(txid=coinbase_txid, verbose=True) @@ -276,9 +261,6 @@ class SegWitTest(BitcoinTestFramework): # tx3 (non-segwit input, paying to a non-segwit output). # tx1 is allowed to appear in the block, but no others. txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996")) - hex_tx = self.nodes[0].gettransaction(txid)['hex'] - tx = tx_from_hex(hex_tx) - assert tx.wit.is_null() # This should not be a segwit input assert txid1 in self.nodes[0].getrawmempool() tx1_hex = self.nodes[0].gettransaction(txid1)['hex'] @@ -613,11 +595,6 @@ class SegWitTest(BitcoinTestFramework): assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid) assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid) - self.log.info('Test negative and unknown rpcserialversion throw an init error') - self.stop_node(0) - self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=-1"], "Error: rpcserialversion must be non-negative.") - self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=100"], "Error: Unknown rpcserialversion requested.") - def mine_and_test_listunspent(self, script_list, ismine): utxo = find_spendable_utxo(self.nodes[0], 50) tx = CTransaction() diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index e32319961e..e85541d0ec 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -95,7 +95,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, assert_equal, - random_bytes, ) from test_framework.wallet_util import generate_keypair from test_framework.key import ( @@ -105,7 +104,7 @@ from test_framework.key import ( tweak_add_privkey, ECKey, ) -from test_framework import secp256k1 +from test_framework.crypto import secp256k1 from test_framework.address import ( hash160, program_to_witness, @@ -694,7 +693,7 @@ def spenders_taproot_active(): # Generate an invalid public key while True: - invalid_pub = random_bytes(32) + invalid_pub = random.randbytes(32) if not secp256k1.GE.is_valid_x(int.from_bytes(invalid_pub, 'big')): break @@ -710,7 +709,7 @@ def spenders_taproot_active(): # == Tests for signature hashing == # Run all tests once with no annex, and once with a valid random annex. - for annex in [None, lambda _: bytes([ANNEX_TAG]) + random_bytes(random.randrange(0, 250))]: + for annex in [None, lambda _: bytes([ANNEX_TAG]) + random.randbytes(random.randrange(0, 250))]: # Non-empty annex is non-standard no_annex = annex is None @@ -739,7 +738,7 @@ def spenders_taproot_active(): scripts = [ ("pk_codesep", CScript(random_checksig_style(pubs[1]) + bytes([OP_CODESEPARATOR]))), # codesep after checksig ("codesep_pk", CScript(bytes([OP_CODESEPARATOR]) + random_checksig_style(pubs[1]))), # codesep before checksig - ("branched_codesep", CScript([random_bytes(random.randrange(2, 511)), OP_DROP, OP_IF, OP_CODESEPARATOR, pubs[0], OP_ELSE, OP_CODESEPARATOR, pubs[1], OP_ENDIF, OP_CHECKSIG])), # branch dependent codesep + ("branched_codesep", CScript([random.randbytes(random.randrange(2, 511)), OP_DROP, OP_IF, OP_CODESEPARATOR, pubs[0], OP_ELSE, OP_CODESEPARATOR, pubs[1], OP_ENDIF, OP_CHECKSIG])), # branch dependent codesep # Note that the first data push in the "branched_codesep" script has the purpose of # randomizing the sighash, both by varying script size and content. In order to # avoid MINIMALDATA script verification errors caused by not-minimal-encoded data @@ -792,8 +791,8 @@ def spenders_taproot_active(): add_spender(spenders, "siglen/empty_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_CHECKSIGVERIFY) add_spender(spenders, "siglen/empty_cs", tap=tap, key=secs[2], leaf="cs_pos", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_NO_SUCCESS) add_spender(spenders, "siglen/empty_csa", tap=tap, key=secs[2], leaf="csa_pos", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_NO_SUCCESS) - add_spender(spenders, "siglen/empty_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random_bytes(random.randrange(1, 63))}, **ERR_SIG_SIZE) - add_spender(spenders, "siglen/empty_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random_bytes(random.randrange(66, 100))}, **ERR_SIG_SIZE) + add_spender(spenders, "siglen/empty_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random.randbytes(random.randrange(1, 63))}, **ERR_SIG_SIZE) + add_spender(spenders, "siglen/empty_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random.randbytes(random.randrange(66, 100))}, **ERR_SIG_SIZE) # Appending a zero byte to signatures invalidates them add_spender(spenders, "siglen/padzero_keypath", tap=tap, key=secs[3], hashtype=hashtype, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE)) add_spender(spenders, "siglen/padzero_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE)) @@ -874,14 +873,14 @@ def spenders_taproot_active(): # Test that empty witnesses are invalid. add_spender(spenders, "spendpath/emptywit", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"witness": []}, **ERR_EMPTY_WITNESS) # Test that adding garbage to the control block invalidates it. - add_spender(spenders, "spendpath/padlongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random_bytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE) + add_spender(spenders, "spendpath/padlongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random.randbytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE) # Test that truncating the control block invalidates it. add_spender(spenders, "spendpath/trunclongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:random.randrange(1, 32)]}, **ERR_CONTROLBLOCK_SIZE) scripts = [("s", CScript([pubs[0], OP_CHECKSIG]))] tap = taproot_construct(pubs[1], scripts) # Test that adding garbage to the control block invalidates it. - add_spender(spenders, "spendpath/padshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random_bytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE) + add_spender(spenders, "spendpath/padshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random.randbytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE) # Test that truncating the control block invalidates it. add_spender(spenders, "spendpath/truncshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:random.randrange(1, 32)]}, **ERR_CONTROLBLOCK_SIZE) # Test that truncating the control block to 1 byte ("-1 Merkle length") invalidates it @@ -970,9 +969,9 @@ def spenders_taproot_active(): # 24) Script that expects an input stack of 1001 elements ("t24", CScript([OP_DROP] * 1000 + [pubs[1], OP_CHECKSIG])), # 25) Script that pushes a MAX_SCRIPT_ELEMENT_SIZE-bytes element - ("t25", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE), OP_DROP, pubs[1], OP_CHECKSIG])), + ("t25", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE), OP_DROP, pubs[1], OP_CHECKSIG])), # 26) Script that pushes a (MAX_SCRIPT_ELEMENT_SIZE+1)-bytes element - ("t26", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, pubs[1], OP_CHECKSIG])), + ("t26", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, pubs[1], OP_CHECKSIG])), # 27) CHECKSIGADD that must fail because numeric argument number is >4 bytes ("t27", CScript([CScriptNum(OVERSIZE_NUMBER), pubs[1], OP_CHECKSIGADD])), # 28) Pushes random CScriptNum value, checks OP_CHECKSIGADD result @@ -1005,9 +1004,9 @@ def spenders_taproot_active(): "tap": tap, } # Test that MAX_SCRIPT_ELEMENT_SIZE byte stack element inputs are valid, but not one more (and 80 bytes is standard but 81 is not). - add_spender(spenders, "tapscript/inputmaxlimit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random_bytes(MAX_SCRIPT_ELEMENT_SIZE)], failure={"inputs": [getter("sign"), random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1)]}, **ERR_PUSH_LIMIT) - add_spender(spenders, "tapscript/input80limit", leaf="t0", **common, inputs=[getter("sign"), random_bytes(80)]) - add_spender(spenders, "tapscript/input81limit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random_bytes(81)]) + add_spender(spenders, "tapscript/inputmaxlimit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random.randbytes(MAX_SCRIPT_ELEMENT_SIZE)], failure={"inputs": [getter("sign"), random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1)]}, **ERR_PUSH_LIMIT) + add_spender(spenders, "tapscript/input80limit", leaf="t0", **common, inputs=[getter("sign"), random.randbytes(80)]) + add_spender(spenders, "tapscript/input81limit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random.randbytes(81)]) # Test that OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY cause failure, but OP_CHECKSIG and OP_CHECKSIGVERIFY work. add_spender(spenders, "tapscript/disabled_checkmultisig", leaf="t1", **common, **SINGLE_SIG, failure={"leaf": "t3"}, **ERR_TAPSCRIPT_CHECKMULTISIG) add_spender(spenders, "tapscript/disabled_checkmultisigverify", leaf="t2", **common, **SINGLE_SIG, failure={"leaf": "t4"}, **ERR_TAPSCRIPT_CHECKMULTISIG) @@ -1062,7 +1061,7 @@ def spenders_taproot_active(): # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGVERIFY. lambda n, pk: (CScript([OP_DROP, pk, OP_0, OP_IF, OP_2DUP, OP_CHECKSIGVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_2, OP_SWAP, OP_CHECKSIGADD, OP_3, OP_EQUAL]), n + 1), # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIG. - lambda n, pk: (CScript([random_bytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1), + lambda n, pk: (CScript([random.randbytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1), # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD. lambda n, pk: (CScript([OP_DROP, pk, OP_1, OP_IF, OP_ELSE, OP_2DUP, OP_6, OP_SWAP, OP_CHECKSIGADD, OP_7, OP_EQUALVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_8, OP_SWAP, OP_CHECKSIGADD, OP_9, OP_EQUAL]), n + 1), # n+1 OP_CHECKSIGs, but also one OP_CHECKSIG with an empty signature. @@ -1070,9 +1069,9 @@ def spenders_taproot_active(): # n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature. lambda n, pk: (CScript([OP_DROP, OP_0, OP_10, pk, OP_CHECKSIGADD, OP_10, OP_EQUALVERIFY, pk] + [OP_2DUP, OP_16, OP_SWAP, OP_CHECKSIGADD, b'\x11', OP_EQUALVERIFY] * n + [OP_CHECKSIG]), n + 1), ] - for annex in [None, bytes([ANNEX_TAG]) + random_bytes(random.randrange(1000))]: + for annex in [None, bytes([ANNEX_TAG]) + random.randbytes(random.randrange(1000))]: for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]: - for pubkey in [pubs[1], random_bytes(random.choice([x for x in range(2, 81) if x != 32]))]: + for pubkey in [pubs[1], random.randbytes(random.choice([x for x in range(2, 81) if x != 32]))]: for fn_num, fn in enumerate(SIGOPS_RATIO_SCRIPTS): merkledepth = random.randrange(129) @@ -1109,7 +1108,7 @@ def spenders_taproot_active(): scripts = [scripts, random.choice(PARTNER_MERKLE_FN)] tap = taproot_construct(pubs[0], scripts) standard = annex is None and dummylen <= 80 and len(pubkey) == 32 - add_spender(spenders, "tapscript/sigopsratio_%i" % fn_num, tap=tap, leaf="s", annex=annex, hashtype=hashtype, key=secs[1], inputs=[getter("sign"), random_bytes(dummylen)], standard=standard, failure={"inputs": [getter("sign"), random_bytes(dummylen - 1)]}, **ERR_SIGOPS_RATIO) + add_spender(spenders, "tapscript/sigopsratio_%i" % fn_num, tap=tap, leaf="s", annex=annex, hashtype=hashtype, key=secs[1], inputs=[getter("sign"), random.randbytes(dummylen)], standard=standard, failure={"inputs": [getter("sign"), random.randbytes(dummylen - 1)]}, **ERR_SIGOPS_RATIO) # Future leaf versions for leafver in range(0, 0x100, 2): @@ -1123,8 +1122,8 @@ def spenders_taproot_active(): ("return_unkver", CScript([OP_RETURN]), leafver), ("undecodable_c0", CScript([OP_PUSHDATA1])), ("undecodable_unkver", CScript([OP_PUSHDATA1]), leafver), - ("bigpush_c0", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP])), - ("bigpush_unkver", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP]), leafver), + ("bigpush_c0", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP])), + ("bigpush_unkver", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP]), leafver), ("1001push_c0", CScript([OP_0] * 1001)), ("1001push_unkver", CScript([OP_0] * 1001), leafver), ] @@ -1153,8 +1152,8 @@ def spenders_taproot_active(): ("undecodable_success", CScript([opcode, OP_PUSHDATA1])), ("undecodable_nop", CScript([OP_NOP, OP_PUSHDATA1])), ("undecodable_bypassed_success", CScript([OP_PUSHDATA1, OP_2, opcode])), - ("bigpush_success", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, opcode])), - ("bigpush_nop", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, OP_NOP])), + ("bigpush_success", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, opcode])), + ("bigpush_nop", CScript([random.randbytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, OP_NOP])), ("1001push_success", CScript([OP_0] * 1001 + [opcode])), ("1001push_nop", CScript([OP_0] * 1001 + [OP_NOP])), ] diff --git a/test/functional/feature_txindex_compatibility.py b/test/functional/feature_txindex_compatibility.py deleted file mode 100755 index 939271b385..0000000000 --- a/test/functional/feature_txindex_compatibility.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2021-2022 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test that legacy txindex will be disabled on upgrade. - -Previous releases are required by this test, see test/README.md. -""" - -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 = 2 - self.extra_args = [ - ["-reindex", "-txindex"], - [], - ] - - def skip_test_if_missing_module(self): - self.skip_if_no_previous_releases() - - def setup_network(self): - self.add_nodes( - self.num_nodes, - self.extra_args, - versions=[ - 160300, # Last release with legacy txindex - None, # For MiniWallet, without migration code - ], - ) - self.start_nodes() - self.connect_nodes(0, 1) - - def run_test(self): - mini_wallet = MiniWallet(self.nodes[1]) - spend_utxo = mini_wallet.get_utxo() - mini_wallet.send_self_transfer(from_node=self.nodes[1], utxo_to_spend=spend_utxo) - 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("Drop legacy txindex") - drop_index_chain_dir = self.nodes[1].chain_path - shutil.rmtree(drop_index_chain_dir) - shutil.copytree(legacy_chain_dir, drop_index_chain_dir) - # Build txindex from scratch and check there is no error this time - self.start_node(1, extra_args=["-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() - - -if __name__ == "__main__": - TxindexCompatibilityTest().main() diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index ce2a5ab8ac..be154b411f 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -11,7 +11,7 @@ from test_framework.messages import ( COutPoint, from_hex, ) -from test_framework.muhash import MuHash3072 +from test_framework.crypto.muhash import MuHash3072 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.wallet import MiniWallet diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index c0679c5ba9..b81eae2506 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -65,7 +65,7 @@ class RESTTest (BitcoinTestFramework): body: str = '', status: int = 200, ret_type: RetType = RetType.JSON, - query_params: Optional[typing.Dict[str, typing.Any]] = None, + query_params: Optional[dict[str, typing.Any]] = None, ) -> typing.Union[http.client.HTTPResponse, bytes, str, None]: rest_uri = '/rest' + uri if req_type in ReqType: diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index fd3e219586..a126f164aa 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -28,7 +28,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): def setup_network(self): self.add_nodes(self.num_nodes, versions=[ - 200100, # Last release with previous mempool format + 200100, # Last release without unbroadcast serialization and without XOR None, ]) self.start_nodes() @@ -59,7 +59,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): old_node_mempool.rename(new_node_mempool) self.log.info("Start new node and verify mempool contains the tx") - self.start_node(1) + self.start_node(1, extra_args=["-persistmempoolv1=1"]) assert old_tx_hash in new_node.getrawmempool() self.log.info("Add unbroadcasted tx to mempool on new node and shutdown") diff --git a/test/functional/mempool_datacarrier.py b/test/functional/mempool_datacarrier.py index 951bf37ae8..2e27aa988e 100755 --- a/test/functional/mempool_datacarrier.py +++ b/test/functional/mempool_datacarrier.py @@ -13,12 +13,11 @@ from test_framework.script import ( ) from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import TestNode -from test_framework.util import ( - assert_raises_rpc_error, - random_bytes, -) +from test_framework.util import assert_raises_rpc_error from test_framework.wallet import MiniWallet +from random import randbytes + class DataCarrierTest(BitcoinTestFramework): def set_test_params(self): @@ -48,11 +47,11 @@ class DataCarrierTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) # By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes). - default_size_data = random_bytes(MAX_OP_RETURN_RELAY - 3) - too_long_data = random_bytes(MAX_OP_RETURN_RELAY - 2) - small_data = random_bytes(MAX_OP_RETURN_RELAY - 4) - one_byte = random_bytes(1) - zero_bytes = random_bytes(0) + default_size_data = randbytes(MAX_OP_RETURN_RELAY - 3) + too_long_data = randbytes(MAX_OP_RETURN_RELAY - 2) + small_data = randbytes(MAX_OP_RETURN_RELAY - 4) + one_byte = randbytes(1) + zero_bytes = randbytes(0) self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.") self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True) diff --git a/test/functional/mempool_dust.py b/test/functional/mempool_dust.py index f4e385a112..e0c026207a 100755 --- a/test/functional/mempool_dust.py +++ b/test/functional/mempool_dust.py @@ -40,6 +40,7 @@ DUST_RELAY_TX_FEE = 3000 # default setting [sat/kvB] class DustRelayFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [['-permitbaremultisig']] def test_dust_output(self, node: TestNode, dust_relay_fee: Decimal, output_script: CScript, type_desc: str) -> None: @@ -101,7 +102,7 @@ class DustRelayFeeTest(BitcoinTestFramework): else: dust_parameter = f"-dustrelayfee={dustfee_btc_kvb:.8f}" self.log.info(f"Test dust limit setting {dust_parameter} ({dustfee_sat_kvb} sat/kvB)...") - self.restart_node(0, extra_args=[dust_parameter]) + self.restart_node(0, extra_args=[dust_parameter, "-permitbaremultisig"]) for output_script, description in output_scripts: self.test_dust_output(self.nodes[0], dustfee_btc_kvb, output_script, description) diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index a1147f70f3..6215610c31 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -125,8 +125,9 @@ class MempoolLimitTest(BitcoinTestFramework): utxo_to_spend=tx_B["new_utxo"], confirmed_only=True ) - - assert_raises_rpc_error(-26, "too-long-mempool-chain", node.submitpackage, [tx_B["hex"], tx_C["hex"]]) + res = node.submitpackage([tx_B["hex"], tx_C["hex"]]) + assert_equal(res["package_msg"], "transaction failed") + assert "too-long-mempool-chain" in res["tx-results"][tx_C["wtxid"]]["error"] def test_mid_package_eviction(self): node = self.nodes[0] @@ -205,7 +206,7 @@ class MempoolLimitTest(BitcoinTestFramework): # Package should be submitted, temporarily exceeding maxmempool, and then evicted. with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]): - assert_raises_rpc_error(-26, "mempool full", node.submitpackage, package_hex) + assert_equal(node.submitpackage(package_hex)["package_msg"], "transaction failed") # Maximum size must never be exceeded. assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"]) @@ -273,7 +274,9 @@ class MempoolLimitTest(BitcoinTestFramework): package_hex = [cpfp_parent["hex"], replacement_tx["hex"], child["hex"]] # Package should be submitted, temporarily exceeding maxmempool, and then evicted. - assert_raises_rpc_error(-26, "bad-txns-inputs-missingorspent", node.submitpackage, package_hex) + res = node.submitpackage(package_hex) + assert_equal(res["package_msg"], "transaction failed") + assert len([tx_res for _, tx_res in res["tx-results"].items() if "error" in tx_res and tx_res["error"] == "bad-txns-inputs-missingorspent"]) # Maximum size must never be exceeded. assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"]) @@ -321,6 +324,7 @@ class MempoolLimitTest(BitcoinTestFramework): package_txns.append(tx_child) submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns]) + assert_equal(submitpackage_result["package_msg"], "success") rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]] poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]] @@ -366,7 +370,9 @@ class MempoolLimitTest(BitcoinTestFramework): assert_greater_than(worst_feerate_btcvb, (parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize())) assert_greater_than(mempoolmin_feerate, (parent_fee) / (tx_parent_just_below["tx"].get_vsize())) assert_greater_than((parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()), mempoolmin_feerate / 1000) - assert_raises_rpc_error(-26, "mempool full", node.submitpackage, [tx_parent_just_below["hex"], tx_child_just_above["hex"]]) + res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]]) + for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]: + assert_equal(res["tx-results"][wtxid]["error"], "mempool full") self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error') self.stop_node(0) diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py index fbec6d0dc8..2e7850fb40 100755 --- a/test/functional/mempool_sigoplimit.py +++ b/test/functional/mempool_sigoplimit.py @@ -34,7 +34,6 @@ from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, - assert_raises_rpc_error, ) from test_framework.wallet import MiniWallet from test_framework.wallet_util import generate_keypair @@ -140,7 +139,7 @@ class BytesPerSigOpTest(BitcoinTestFramework): self.log.info("Test a overly-large sigops-vbyte hits package limits") # Make a 2-transaction package which fails vbyte checks even though # separately they would work. - self.restart_node(0, extra_args=["-bytespersigop=5000"] + self.extra_args[0]) + self.restart_node(0, extra_args=["-bytespersigop=5000","-permitbaremultisig=1"] + self.extra_args[0]) def create_bare_multisig_tx(utxo_to_spend=None): _, pubkey = generate_keypair() @@ -169,7 +168,8 @@ class BytesPerSigOpTest(BitcoinTestFramework): assert_equal([x["package-error"] for x in packet_test], ["package-mempool-limits", "package-mempool-limits"]) # When we actually try to submit, the parent makes it into the mempool, but the child would exceed ancestor vsize limits - assert_raises_rpc_error(-26, "too-long-mempool-chain", self.nodes[0].submitpackage, [tx_parent.serialize().hex(), tx_child.serialize().hex()]) + res = self.nodes[0].submitpackage([tx_parent.serialize().hex(), tx_child.serialize().hex()]) + assert "too-long-mempool-chain" in res["tx-results"][tx_child.getwtxid()]["error"] assert tx_parent.rehash() in self.nodes[0].getrawmempool() # Transactions are tiny in weight diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py index 63cd10896d..2adcaf178c 100755 --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -270,15 +270,16 @@ class AddrTest(BitcoinTestFramework): full_outbound_peer.sync_with_ping() assert full_outbound_peer.getaddr_received() - self.log.info('Check that we do not send a getaddr message upon connecting to a block-relay-only peer') + self.log.info('Check that we do not send a getaddr message to a block-relay-only or inbound peer') block_relay_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=1, connection_type="block-relay-only") block_relay_peer.sync_with_ping() assert_equal(block_relay_peer.getaddr_received(), False) - self.log.info('Check that we answer getaddr messages only from inbound peers') inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(send_getaddr=False)) inbound_peer.sync_with_ping() + assert_equal(inbound_peer.getaddr_received(), False) + self.log.info('Check that we answer getaddr messages only from inbound peers') # Add some addresses to addrman for i in range(1000): first_octet = i >> 8 diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index 665f57365f..62d55cc101 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -11,6 +11,7 @@ from test_framework.messages import ( COIN, MAX_BLOOM_FILTER_SIZE, MAX_BLOOM_HASH_FUNCS, + MSG_WTX, MSG_BLOCK, MSG_FILTERED_BLOCK, msg_filteradd, @@ -135,14 +136,22 @@ class FilterTest(BitcoinTestFramework): self.log.info("Check that a node with bloom filters enabled services p2p mempool messages") filter_peer = P2PBloomFilter() - self.log.debug("Create a tx relevant to the peer before connecting") - txid = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)["txid"] + self.log.info("Create two tx before connecting, one relevant to the node another that is not") + rel_txid = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=1 * COIN)["txid"] + irr_result = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=2 * COIN) + irr_txid = irr_result["txid"] + irr_wtxid = irr_result["wtxid"] - self.log.debug("Send a mempool msg after connecting and check that the tx is received") + self.log.info("Send a mempool msg after connecting and check that the relevant tx is announced") self.nodes[0].add_p2p_connection(filter_peer) filter_peer.send_and_ping(filter_peer.watch_filter_init) filter_peer.send_message(msg_mempool()) - filter_peer.wait_for_tx(txid) + filter_peer.wait_for_tx(rel_txid) + + self.log.info("Request the irrelevant transaction even though it was not announced") + filter_peer.send_message(msg_getdata([CInv(t=MSG_WTX, h=int(irr_wtxid, 16))])) + self.log.info("We should get it anyway because it was in the mempool on connection to peer") + filter_peer.wait_for_tx(irr_txid) 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") diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 2fb88b828f..4916d36ab7 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -216,7 +216,7 @@ class InvalidMessagesTest(BitcoinTestFramework): self.test_addrv2('unrecognized network', [ 'received: addrv2 (25 bytes)', - '9.9.9.9:8333 mapped', + '9.9.9.9:8333', 'Added 1 addresses', ], bytes.fromhex( diff --git a/test/functional/p2p_net_deadlock.py b/test/functional/p2p_net_deadlock.py index f69fe52146..1a357b944b 100755 --- a/test/functional/p2p_net_deadlock.py +++ b/test/functional/p2p_net_deadlock.py @@ -5,7 +5,7 @@ import threading from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import random_bytes +from random import randbytes class NetDeadlockTest(BitcoinTestFramework): @@ -18,7 +18,7 @@ class NetDeadlockTest(BitcoinTestFramework): node1 = self.nodes[1] self.log.info("Simultaneously send a large message on both sides") - rand_msg = random_bytes(4000000).hex() + rand_msg = randbytes(4000000).hex() thread0 = threading.Thread(target=node0.sendmsgtopeer, args=(0, "unknown", rand_msg)) thread1 = threading.Thread(target=node1.sendmsgtopeer, args=(0, "unknown", rand_msg)) diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index a56afbcf7b..89c35e943b 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -8,7 +8,15 @@ Tests that a node configured with -prune=550 signals NODE_NETWORK_LIMITED correc and that it responds to getdata requests for blocks correctly: - send a block within 288 + 2 of the tip - disconnect peers who request blocks older than that.""" -from test_framework.messages import CInv, MSG_BLOCK, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS +from test_framework.messages import ( + CInv, + MSG_BLOCK, + NODE_NETWORK_LIMITED, + NODE_P2P_V2, + NODE_WITNESS, + msg_getdata, + msg_verack, +) from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -50,6 +58,8 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED + if self.options.v2transport: + expected_services |= NODE_P2P_V2 self.log.info("Check that node has signalled expected services.") assert_equal(node.nServices, expected_services) diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index a308577c02..b4fa5099d8 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -68,11 +68,14 @@ class TimeoutsTest(BitcoinTestFramework): with self.nodes[0].assert_debug_log(['Unsupported message "ping" prior to verack from peer=0']): no_verack_node.send_message(msg_ping()) - with self.nodes[0].assert_debug_log(['non-version message before version handshake. Message "ping" from peer=1']): - no_version_node.send_message(msg_ping()) - self.mock_forward(1) + # With v2, non-version messages before the handshake would be interpreted as part of the key exchange. + # Therefore, don't execute this part of the test if v2transport is chosen. + if not self.options.v2transport: + with self.nodes[0].assert_debug_log(['non-version message before version handshake. Message "ping" from peer=1']): + no_version_node.send_message(msg_ping()) + self.mock_forward(1) assert "version" in no_verack_node.last_message assert no_verack_node.is_connected @@ -80,11 +83,12 @@ class TimeoutsTest(BitcoinTestFramework): assert no_send_node.is_connected no_verack_node.send_message(msg_ping()) - no_version_node.send_message(msg_ping()) + if not self.options.v2transport: + no_version_node.send_message(msg_ping()) expected_timeout_logs = [ "version handshake timeout peer=0", - "socket no message in first 3 seconds, 1 0 peer=1", + f"socket no message in first 3 seconds, {'0' if self.options.v2transport else '1'} 0 peer=1", "socket no message in first 3 seconds, 0 0 peer=2", ] @@ -100,5 +104,6 @@ class TimeoutsTest(BitcoinTestFramework): extra_args=['-peertimeout=0'], ) + if __name__ == '__main__': TimeoutsTest().main() diff --git a/test/functional/p2p_v2_transport.py b/test/functional/p2p_v2_transport.py index 5ad2194b84..72d22cb77f 100755 --- a/test/functional/p2p_v2_transport.py +++ b/test/functional/p2p_v2_transport.py @@ -48,7 +48,7 @@ class V2TransportTest(BitcoinTestFramework): assert_equal(self.nodes[1].getblockcount(), 5) # verify there is a v2 connection between node 0 and 1 node_0_info = self.nodes[0].getpeerinfo() - node_1_info = self.nodes[0].getpeerinfo() + node_1_info = self.nodes[1].getpeerinfo() assert_equal(len(node_0_info), 1) assert_equal(len(node_1_info), 1) assert_equal(node_0_info[0]["transport_protocol_type"], "v2") @@ -133,9 +133,8 @@ class V2TransportTest(BitcoinTestFramework): V1_PREFIX = MAGIC_BYTES["regtest"] + b"version\x00\x00\x00\x00\x00" assert_equal(len(V1_PREFIX), 16) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - num_peers = len(self.nodes[0].getpeerinfo()) - s.connect(("127.0.0.1", p2p_port(0))) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1) + with self.nodes[0].wait_for_new_peer(): + s.connect(("127.0.0.1", p2p_port(0))) s.sendall(V1_PREFIX[:-1]) assert_equal(self.nodes[0].getpeerinfo()[-1]["transport_protocol_type"], "detecting") s.sendall(bytes([V1_PREFIX[-1]])) # send out last prefix byte @@ -144,22 +143,23 @@ class V2TransportTest(BitcoinTestFramework): # Check wrong network prefix detection (hits if the next 12 bytes correspond to a v1 version message) wrong_network_magic_prefix = MAGIC_BYTES["signet"] + V1_PREFIX[4:] with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect(("127.0.0.1", p2p_port(0))) + with self.nodes[0].wait_for_new_peer(): + s.connect(("127.0.0.1", p2p_port(0))) with self.nodes[0].assert_debug_log(["V2 transport error: V1 peer with wrong MessageStart"]): s.sendall(wrong_network_magic_prefix + b"somepayload") # Check detection of missing garbage terminator (hits after fixed amount of data if terminator never matches garbage) MAX_KEY_GARB_AND_GARBTERM_LEN = 64 + 4095 + 16 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - num_peers = len(self.nodes[0].getpeerinfo()) - s.connect(("127.0.0.1", p2p_port(0))) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1) + with self.nodes[0].wait_for_new_peer(): + s.connect(("127.0.0.1", p2p_port(0))) s.sendall(b'\x00' * (MAX_KEY_GARB_AND_GARBTERM_LEN - 1)) self.wait_until(lambda: self.nodes[0].getpeerinfo()[-1]["bytesrecv"] == MAX_KEY_GARB_AND_GARBTERM_LEN - 1) with self.nodes[0].assert_debug_log(["V2 transport error: missing garbage terminator"]): + peer_id = self.nodes[0].getpeerinfo()[-1]["id"] s.sendall(b'\x00') # send out last byte # should disconnect immediately - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers) + self.wait_until(lambda: not peer_id in [p["id"] for p in self.nodes[0].getpeerinfo()]) if __name__ == '__main__': diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 53163720bb..9b7743cafa 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -58,6 +58,7 @@ TIME_RANGE_STEP = 600 # ten-minute steps TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP +DIFFICULTY_ADJUSTMENT_INTERVAL = 2016 class BlockchainTest(BitcoinTestFramework): @@ -436,7 +437,6 @@ class BlockchainTest(BitcoinTestFramework): def _test_getnetworkhashps(self): self.log.info("Test getnetworkhashps") - hashes_per_second = self.nodes[0].getnetworkhashps() assert_raises_rpc_error( -3, textwrap.dedent(""" @@ -448,9 +448,47 @@ class BlockchainTest(BitcoinTestFramework): """).strip(), lambda: self.nodes[0].getnetworkhashps("a", []), ) + assert_raises_rpc_error( + -8, + "Block does not exist at specified height", + lambda: self.nodes[0].getnetworkhashps(100, self.nodes[0].getblockcount() + 1), + ) + assert_raises_rpc_error( + -8, + "Block does not exist at specified height", + lambda: self.nodes[0].getnetworkhashps(100, -10), + ) + assert_raises_rpc_error( + -8, + "Invalid nblocks. Must be a positive number or -1.", + lambda: self.nodes[0].getnetworkhashps(-100), + ) + assert_raises_rpc_error( + -8, + "Invalid nblocks. Must be a positive number or -1.", + lambda: self.nodes[0].getnetworkhashps(0), + ) + + # Genesis block height estimate should return 0 + hashes_per_second = self.nodes[0].getnetworkhashps(100, 0) + assert_equal(hashes_per_second, 0) + # This should be 2 hashes every 10 minutes or 1/300 + hashes_per_second = self.nodes[0].getnetworkhashps() assert abs(hashes_per_second * 300 - 1) < 0.0001 + # Test setting the first param of getnetworkhashps to -1 returns the average network + # hashes per second from the last difficulty change. + current_block_height = self.nodes[0].getmininginfo()['blocks'] + blocks_since_last_diff_change = current_block_height % DIFFICULTY_ADJUSTMENT_INTERVAL + 1 + expected_hashes_per_second_since_diff_change = self.nodes[0].getnetworkhashps(blocks_since_last_diff_change) + + assert_equal(self.nodes[0].getnetworkhashps(-1), expected_hashes_per_second_since_diff_change) + + # Ensure long lookups get truncated to chain length + hashes_per_second = self.nodes[0].getnetworkhashps(self.nodes[0].getblockcount() + 1000) + assert hashes_per_second > 0.003 + def _test_stopatheight(self): self.log.info("Test stopping at height") assert_equal(self.nodes[0].getblockcount(), HEIGHT) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 50a022fc7e..e1820b0f55 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -9,6 +9,7 @@ Tests correspond to code in rpc/net.cpp. from decimal import Decimal from itertools import product +import platform import time import test_framework.messages @@ -110,7 +111,7 @@ class NetTest(BitcoinTestFramework): no_version_peer_id = 2 no_version_peer_conntime = int(time.time()) self.nodes[0].setmocktime(no_version_peer_conntime) - with self.nodes[0].assert_debug_log([f"Added connection peer={no_version_peer_id}"]): + with self.nodes[0].wait_for_new_peer(): no_version_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) self.nodes[0].setmocktime(0) peer_info = self.nodes[0].getpeerinfo()[no_version_peer_id] @@ -150,7 +151,7 @@ class NetTest(BitcoinTestFramework): "synced_blocks": -1, "synced_headers": -1, "timeoffset": 0, - "transport_protocol_type": "v1", + "transport_protocol_type": "v1" if not self.options.v2transport else "detecting", "version": 0, }, ) @@ -160,19 +161,23 @@ class NetTest(BitcoinTestFramework): def test_getnettotals(self): self.log.info("Test getnettotals") # Test getnettotals and getpeerinfo by doing a ping. The bytes - # sent/received should increase by at least the size of one ping (32 - # bytes) and one pong (32 bytes). + # sent/received should increase by at least the size of one ping + # and one pong. Both have a payload size of 8 bytes, but the total + # size depends on the used p2p version: + # - p2p v1: 24 bytes (header) + 8 bytes (payload) = 32 bytes + # - p2p v2: 21 bytes (header/tag with short-id) + 8 bytes (payload) = 29 bytes + ping_size = 32 if not self.options.v2transport else 29 net_totals_before = self.nodes[0].getnettotals() peer_info_before = self.nodes[0].getpeerinfo() self.nodes[0].ping() - self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + 32 * 2), timeout=1) - self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + 32 * 2), timeout=1) + self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + ping_size * 2), timeout=1) + self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + ping_size * 2), timeout=1) for peer_before in peer_info_before: peer_after = lambda: next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id']) - self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + 32, timeout=1) - self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + 32, timeout=1) + self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + ping_size, timeout=1) + self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + ping_size, timeout=1) def test_getnetworkinfo(self): self.log.info("Test getnetworkinfo") @@ -215,8 +220,13 @@ class NetTest(BitcoinTestFramework): # add a node (node2) to node0 ip_port = "127.0.0.1:{}".format(p2p_port(2)) self.nodes[0].addnode(node=ip_port, command='add') + # try to add an equivalent ip + # (note that OpenBSD doesn't support the IPv4 shorthand notation with omitted zero-bytes) + if platform.system() != "OpenBSD": + ip_port2 = "127.1:{}".format(p2p_port(2)) + assert_raises_rpc_error(-23, "Node already added", self.nodes[0].addnode, node=ip_port2, command='add') # check that the node has indeed been added - added_nodes = self.nodes[0].getaddednodeinfo(ip_port) + added_nodes = self.nodes[0].getaddednodeinfo() assert_equal(len(added_nodes), 1) assert_equal(added_nodes[0]['addednode'], ip_port) # check that node cannot be added again @@ -342,7 +352,10 @@ class NetTest(BitcoinTestFramework): node = self.nodes[0] self.restart_node(0) - self.connect_nodes(0, 1) + # we want to use a p2p v1 connection here in order to ensure + # a peer id of zero (a downgrade from v2 to v1 would lead + # to an increase of the peer id) + self.connect_nodes(0, 1, peer_advertises_v2=False) self.log.info("Test sendmsgtopeer") self.log.debug("Send a valid message") diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index 5644a9f5a8..664f2df3f1 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -304,6 +304,7 @@ class RPCPackagesTest(BitcoinTestFramework): submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns]) # Check that each result is present, with the correct size and fees + assert_equal(submitpackage_result["package_msg"], "success") for package_txn in package_txns: tx = package_txn["tx"] assert tx.getwtxid() in submitpackage_result["tx-results"] @@ -334,9 +335,26 @@ class RPCPackagesTest(BitcoinTestFramework): self.log.info("Submitpackage only allows packages of 1 child with its parents") # Chain of 3 transactions has too many generations - chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)] + legacy_pool = node.getrawmempool() + chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)] assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex) + assert_equal(legacy_pool, node.getrawmempool()) + # Create a transaction chain such as only the parent gets accepted (by making the child's + # version non-standard). Make sure the parent does get broadcast. + self.log.info("If a package is partially submitted, transactions included in mempool get broadcast") + peer = node.add_p2p_connection(P2PTxInvStore()) + txs = self.wallet.create_self_transfer_chain(chain_length=2) + bad_child = tx_from_hex(txs[1]["hex"]) + bad_child.nVersion = -1 + hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()] + res = node.submitpackage(hex_partial_acceptance) + assert_equal(res["package_msg"], "transaction failed") + first_wtxid = txs[0]["tx"].getwtxid() + assert "error" not in res["tx-results"][first_wtxid] + sec_wtxid = bad_child.getwtxid() + assert_equal(res["tx-results"][sec_wtxid]["error"], "version") + peer.wait_for_broadcast([first_wtxid]) if __name__ == "__main__": RPCPackagesTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 60df48f025..1fd938d18a 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -6,6 +6,7 @@ """ from decimal import Decimal from itertools import product +from random import randbytes from test_framework.descriptors import descsum_create from test_framework.key import H_POINT @@ -38,9 +39,7 @@ from test_framework.util import ( assert_greater_than, assert_greater_than_or_equal, assert_raises_rpc_error, - find_output, find_vout_for_address, - random_bytes, ) from test_framework.wallet_util import ( generate_keypair, @@ -417,16 +416,17 @@ class PSBTTest(BitcoinTestFramework): self.nodes[0].converttopsbt(hexstring=signedtx['hex'], permitsigdata=True) # Create outputs to nodes 1 and 2 + # (note that we intentionally create two different txs here, as we want + # to check that each node is missing prevout data for one of the two + # utxos, see "should only have data for one input" test below) node1_addr = self.nodes[1].getnewaddress() node2_addr = self.nodes[2].getnewaddress() - txid1 = self.nodes[0].sendtoaddress(node1_addr, 13) - txid2 = self.nodes[0].sendtoaddress(node2_addr, 13) - blockhash = self.generate(self.nodes[0], 6)[0] - vout1 = find_output(self.nodes[1], txid1, 13, blockhash=blockhash) - vout2 = find_output(self.nodes[2], txid2, 13, blockhash=blockhash) + utxo1 = self.create_outpoints(self.nodes[0], outputs=[{node1_addr: 13}])[0] + utxo2 = self.create_outpoints(self.nodes[0], outputs=[{node2_addr: 13}])[0] + self.generate(self.nodes[0], 6)[0] # Create a psbt spending outputs from nodes 1 and 2 - psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], {self.nodes[0].getnewaddress():25.999}) + psbt_orig = self.nodes[0].createpsbt([utxo1, utxo2], {self.nodes[0].getnewaddress():25.999}) # Update psbts, should only have data for one input and not the other psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig, False, "ALL")['psbt'] @@ -603,14 +603,9 @@ class PSBTTest(BitcoinTestFramework): # Send to all types of addresses addr1 = self.nodes[1].getnewaddress("", "bech32") - txid1 = self.nodes[0].sendtoaddress(addr1, 11) - vout1 = find_output(self.nodes[0], txid1, 11) addr2 = self.nodes[1].getnewaddress("", "legacy") - txid2 = self.nodes[0].sendtoaddress(addr2, 11) - vout2 = find_output(self.nodes[0], txid2, 11) addr3 = self.nodes[1].getnewaddress("", "p2sh-segwit") - txid3 = self.nodes[0].sendtoaddress(addr3, 11) - vout3 = find_output(self.nodes[0], txid3, 11) + utxo1, utxo2, utxo3 = self.create_outpoints(self.nodes[1], outputs=[{addr1: 11}, {addr2: 11}, {addr3: 11}]) self.sync_all() def test_psbt_input_keys(psbt_input, keys): @@ -618,7 +613,7 @@ class PSBTTest(BitcoinTestFramework): assert_equal(set(keys), set(psbt_input.keys())) # Create a PSBT. None of the inputs are filled initially - psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999}) + psbt = self.nodes[1].createpsbt([utxo1, utxo2, utxo3], {self.nodes[0].getnewaddress():32.999}) decoded = self.nodes[1].decodepsbt(psbt) test_psbt_input_keys(decoded['inputs'][0], []) test_psbt_input_keys(decoded['inputs'][1], []) @@ -641,15 +636,14 @@ class PSBTTest(BitcoinTestFramework): test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script']) # Two PSBTs with a common input should not be joinable - psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')}) + psbt1 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')}) assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated]) # Join two distinct PSBTs addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit") - txid4 = self.nodes[0].sendtoaddress(addr4, 5) - vout4 = find_output(self.nodes[0], txid4, 5) + utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0] self.generate(self.nodes[0], 6) - psbt2 = self.nodes[1].createpsbt([{"txid":txid4, "vout":vout4}], {self.nodes[0].getnewaddress():Decimal('4.999')}) + psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')}) psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt'] psbt2_decoded = self.nodes[0].decodepsbt(psbt2) assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0] @@ -669,11 +663,10 @@ class PSBTTest(BitcoinTestFramework): # Newly created PSBT needs UTXOs and updating addr = self.nodes[1].getnewaddress("", "p2sh-segwit") - txid = self.nodes[0].sendtoaddress(addr, 7) + utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 7}])[0] addrinfo = self.nodes[1].getaddressinfo(addr) - blockhash = self.generate(self.nodes[0], 6)[0] - vout = find_output(self.nodes[0], txid, 7, blockhash=blockhash) - psbt = self.nodes[1].createpsbt([{"txid":txid, "vout":vout}], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')}) + self.generate(self.nodes[0], 6)[0] + psbt = self.nodes[1].createpsbt([utxo], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')}) analyzed = self.nodes[0].analyzepsbt(psbt) assert not analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0]['is_final'] and analyzed['inputs'][0]['next'] == 'updater' and analyzed['next'] == 'updater' @@ -872,9 +865,8 @@ class PSBTTest(BitcoinTestFramework): self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs") addr = self.nodes[0].getnewaddress("", "bech32m") - txid = self.nodes[0].sendtoaddress(addr, 1) - vout = find_vout_for_address(self.nodes[0], txid, addr) - psbt = self.nodes[0].createpsbt([{"txid": txid, "vout": vout}], [{self.nodes[0].getnewaddress(): 0.9999}]) + utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 1}])[0] + psbt = self.nodes[0].createpsbt([utxo], [{self.nodes[0].getnewaddress(): 0.9999}]) signed = self.nodes[0].walletprocesspsbt(psbt) rawtx = signed["hex"] self.nodes[0].sendrawtransaction(rawtx) @@ -893,10 +885,10 @@ class PSBTTest(BitcoinTestFramework): 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) - hash_sha256, preimage_sha256 = random_bytes(32), random_bytes(50) - hash_hash160, preimage_hash160 = random_bytes(20), random_bytes(50) - hash_hash256, preimage_hash256 = random_bytes(32), random_bytes(50) + hash_ripemd160, preimage_ripemd160 = randbytes(20), randbytes(50) + hash_sha256, preimage_sha256 = randbytes(32), randbytes(50) + hash_hash160, preimage_hash160 = randbytes(20), randbytes(50) + hash_hash256, preimage_hash256 = randbytes(32), randbytes(50) tx = CTransaction() tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b""), @@ -962,11 +954,10 @@ class PSBTTest(BitcoinTestFramework): descriptor = descsum_create(f"wpkh({key})") - txid = self.nodes[0].sendtoaddress(address, 1) + utxo = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0] self.sync_all() - vout = find_output(self.nodes[0], txid, 1) - psbt = self.nodes[2].createpsbt([{"txid": txid, "vout": vout}], {self.nodes[0].getnewaddress(): 0.99999}) + psbt = self.nodes[2].createpsbt([utxo], {self.nodes[0].getnewaddress(): 0.99999}) decoded = self.nodes[2].decodepsbt(psbt) test_psbt_input_keys(decoded['inputs'][0], []) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 2395935620..c12865b5e3 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -32,6 +32,7 @@ from test_framework.script import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_greater_than, assert_raises_rpc_error, ) from test_framework.wallet import ( @@ -70,7 +71,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.extra_args = [ ["-txindex"], ["-txindex"], - [], + ["-fastprune", "-prune=1"], ] # whitelist all peers to speed up tx relay / mempool sync for args in self.extra_args: @@ -85,7 +86,6 @@ class RawTransactionsTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.getrawtransaction_tests() - self.getrawtransaction_verbosity_tests() self.createrawtransaction_tests() self.sendrawtransaction_tests() self.sendrawtransaction_testmempoolaccept_tests() @@ -94,6 +94,8 @@ class RawTransactionsTest(BitcoinTestFramework): if self.is_specified_wallet_compiled() and not self.options.descriptors: self.import_deterministic_coinbase_privkeys() self.raw_multisig_transaction_legacy_tests() + self.getrawtransaction_verbosity_tests() + def getrawtransaction_tests(self): tx = self.wallet.send_self_transfer(from_node=self.nodes[0]) @@ -243,6 +245,13 @@ class RawTransactionsTest(BitcoinTestFramework): coin_base = self.nodes[1].getblock(block1)['tx'][0] gottx = self.nodes[1].getrawtransaction(txid=coin_base, verbosity=2, blockhash=block1) assert 'fee' not in gottx + # check that verbosity 2 for a mempool tx will fallback to verbosity 1 + # Do this with a pruned chain, as a regression test for https://github.com/bitcoin/bitcoin/pull/29003 + self.generate(self.nodes[2], 400) + assert_greater_than(self.nodes[2].pruneblockchain(250), 0) + mempool_tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid'] + gottx = self.nodes[2].getrawtransaction(txid=mempool_tx, verbosity=2) + assert 'fee' not in gottx def createrawtransaction_tests(self): self.log.info("Test createrawtransaction") diff --git a/test/functional/test_framework/blockfilter.py b/test/functional/test_framework/blockfilter.py index a30e37ea5b..a16aa3d34f 100644 --- a/test/functional/test_framework/blockfilter.py +++ b/test/functional/test_framework/blockfilter.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Helper routines relevant for compact block filters (BIP158). """ -from .siphash import siphash +from .crypto.siphash import siphash def bip158_basic_element_hash(script_pub_key, N, block_hash): @@ -29,7 +29,7 @@ def bip158_basic_element_hash(script_pub_key, N, block_hash): def bip158_relevant_scriptpubkeys(node, block_hash): - """ Determines the basic filter relvant scriptPubKeys as defined in BIP158: + """ Determines the basic filter relevant scriptPubKeys as defined in BIP158: 'A basic filter MUST contain exactly the following items for each transaction in a block: - The previous output script (the script being spent) for each input, except for diff --git a/test/functional/test_framework/crypto/bip324_cipher.py b/test/functional/test_framework/crypto/bip324_cipher.py new file mode 100644 index 0000000000..56190647f2 --- /dev/null +++ b/test/functional/test_framework/crypto/bip324_cipher.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324 + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + +from .chacha20 import chacha20_block, REKEY_INTERVAL +from .poly1305 import Poly1305 + + +def pad16(x): + if len(x) % 16 == 0: + return b'' + return b'\x00' * (16 - (len(x) % 16)) + + +def aead_chacha20_poly1305_encrypt(key, nonce, aad, plaintext): + """Encrypt a plaintext using ChaCha20Poly1305.""" + ret = bytearray() + msg_len = len(plaintext) + for i in range((msg_len + 63) // 64): + now = min(64, msg_len - 64 * i) + keystream = chacha20_block(key, nonce, i + 1) + for j in range(now): + ret.append(plaintext[j + 64 * i] ^ keystream[j]) + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) + mac_data = aad + pad16(aad) + mac_data += ret + pad16(ret) + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') + ret += poly1305.tag(mac_data) + return bytes(ret) + + +def aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext): + """Decrypt a ChaCha20Poly1305 ciphertext.""" + if len(ciphertext) < 16: + return None + msg_len = len(ciphertext) - 16 + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) + mac_data = aad + pad16(aad) + mac_data += ciphertext[:-16] + pad16(ciphertext[:-16]) + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') + if ciphertext[-16:] != poly1305.tag(mac_data): + return None + ret = bytearray() + for i in range((msg_len + 63) // 64): + now = min(64, msg_len - 64 * i) + keystream = chacha20_block(key, nonce, i + 1) + for j in range(now): + ret.append(ciphertext[j + 64 * i] ^ keystream[j]) + return bytes(ret) + + +class FSChaCha20Poly1305: + """Rekeying wrapper AEAD around ChaCha20Poly1305.""" + def __init__(self, initial_key): + self._key = initial_key + self._packet_counter = 0 + + def _crypt(self, aad, text, is_decrypt): + nonce = ((self._packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') + + (self._packet_counter // REKEY_INTERVAL).to_bytes(8, 'little')) + if is_decrypt: + ret = aead_chacha20_poly1305_decrypt(self._key, nonce, aad, text) + else: + ret = aead_chacha20_poly1305_encrypt(self._key, nonce, aad, text) + if (self._packet_counter + 1) % REKEY_INTERVAL == 0: + rekey_nonce = b"\xFF\xFF\xFF\xFF" + nonce[4:] + self._key = aead_chacha20_poly1305_encrypt(self._key, rekey_nonce, b"", b"\x00" * 32)[:32] + self._packet_counter += 1 + return ret + + def decrypt(self, aad, ciphertext): + return self._crypt(aad, ciphertext, True) + + def encrypt(self, aad, plaintext): + return self._crypt(aad, plaintext, False) + + +# Test vectors from RFC8439 consisting of plaintext, aad, 32 byte key, 12 byte nonce and ciphertext +AEAD_TESTS = [ + # RFC 8439 Example from section 2.8.2 + ["4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + "637265656e20776f756c642062652069742e", + "50515253c0c1c2c3c4c5c6c7", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + [7, 0x4746454443424140], + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + "0691"], + # RFC 8439 Test vector A.5 + ["496e7465726e65742d4472616674732061726520647261667420646f63756d65" + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + "726573732e2fe2809d", + "f33388860000000000004e91", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + [0, 0x0807060504030201], + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38"], + # Test vectors exercising aad and plaintext which are multiples of 16 bytes. + ["8d2d6a8befd9716fab35819eaac83b33269afb9f1a00fddf66095a6c0cd91951" + "a6b7ad3db580be0674c3f0b55f618e34", + "", + "72ddc73f07101282bbbcf853b9012a9f9695fc5d36b303a97fd0845d0314e0c3", + [0x3432b75f, 0xb3585537eb7f4024], + "f760b8224fb2a317b1b07875092606131232a5b86ae142df5df1c846a7f6341a" + "f2564483dd77f836be45e6230808ffe402a6f0a3e8be074b3d1f4ea8a7b09451"], + ["", + "36970d8a704c065de16250c18033de5a400520ac1b5842b24551e5823a3314f3" + "946285171e04a81ebfbe3566e312e74ab80e94c7dd2ff4e10de0098a58d0f503", + "77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021", + [0x1f90da88, 0x75dafa3ef84471a4], + "aaae5bb81e8407c94b2ae86ae0c7efbe"], +] + +FSAEAD_TESTS = [ + ["d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e" + "a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf" + "0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60" + "0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c" + "711191b14d75a72147", + "786cb9b6ebf44288974cf0", + "5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654", + 500, + "9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e" + "0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75" + "4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4" + "4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192" + "8039213de18a5120dc9b7370baca878f50ff254418de3da50c"], + ["8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc" + "44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234", + "", + "3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce", + 60000, + "30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4" + "99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c" + "14b94829deb27f0b1923a2af704ae5d6"], +] + + +class TestFrameworkAEAD(unittest.TestCase): + def test_aead(self): + """ChaCha20Poly1305 AEAD test vectors.""" + for test_vector in AEAD_TESTS: + hex_plain, hex_aad, hex_key, hex_nonce, hex_cipher = test_vector + plain = bytes.fromhex(hex_plain) + aad = bytes.fromhex(hex_aad) + key = bytes.fromhex(hex_key) + nonce = hex_nonce[0].to_bytes(4, 'little') + hex_nonce[1].to_bytes(8, 'little') + + ciphertext = aead_chacha20_poly1305_encrypt(key, nonce, aad, plain) + self.assertEqual(hex_cipher, ciphertext.hex()) + plaintext = aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext) + self.assertEqual(plain, plaintext) + + def test_fschacha20poly1305aead(self): + "FSChaCha20Poly1305 AEAD test vectors." + for test_vector in FSAEAD_TESTS: + hex_plain, hex_aad, hex_key, msg_idx, hex_cipher = test_vector + plain = bytes.fromhex(hex_plain) + aad = bytes.fromhex(hex_aad) + key = bytes.fromhex(hex_key) + + enc_aead = FSChaCha20Poly1305(key) + dec_aead = FSChaCha20Poly1305(key) + + for _ in range(msg_idx): + enc_aead.encrypt(b"", b"") + ciphertext = enc_aead.encrypt(aad, plain) + self.assertEqual(hex_cipher, ciphertext.hex()) + + for _ in range(msg_idx): + dec_aead.decrypt(b"", bytes(16)) + plaintext = dec_aead.decrypt(aad, ciphertext) + self.assertEqual(plain, plaintext) diff --git a/test/functional/test_framework/crypto/chacha20.py b/test/functional/test_framework/crypto/chacha20.py new file mode 100644 index 0000000000..19b6698dfb --- /dev/null +++ b/test/functional/test_framework/crypto/chacha20.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of ChaCha20 cipher and FSChaCha20 for BIP 324 + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + +CHACHA20_INDICES = ( + (0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15), + (0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14) +) + +CHACHA20_CONSTANTS = (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574) +REKEY_INTERVAL = 224 # packets + + +def rotl32(v, bits): + """Rotate the 32-bit value v left by bits bits.""" + bits %= 32 # Make sure the term below does not throw an exception + return ((v << bits) & 0xffffffff) | (v >> (32 - bits)) + + +def chacha20_doubleround(s): + """Apply a ChaCha20 double round to 16-element state array s. + See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439 + """ + for a, b, c, d in CHACHA20_INDICES: + s[a] = (s[a] + s[b]) & 0xffffffff + s[d] = rotl32(s[d] ^ s[a], 16) + s[c] = (s[c] + s[d]) & 0xffffffff + s[b] = rotl32(s[b] ^ s[c], 12) + s[a] = (s[a] + s[b]) & 0xffffffff + s[d] = rotl32(s[d] ^ s[a], 8) + s[c] = (s[c] + s[d]) & 0xffffffff + s[b] = rotl32(s[b] ^ s[c], 7) + + +def chacha20_block(key, nonce, cnt): + """Compute the 64-byte output of the ChaCha20 block function. + Takes as input a 32-byte key, 12-byte nonce, and 32-bit integer counter. + """ + # Initial state. + init = [0] * 16 + init[:4] = CHACHA20_CONSTANTS[:4] + init[4:12] = [int.from_bytes(key[i:i+4], 'little') for i in range(0, 32, 4)] + init[12] = cnt + init[13:16] = [int.from_bytes(nonce[i:i+4], 'little') for i in range(0, 12, 4)] + # Perform 20 rounds. + state = list(init) + for _ in range(10): + chacha20_doubleround(state) + # Add initial values back into state. + for i in range(16): + state[i] = (state[i] + init[i]) & 0xffffffff + # Produce byte output + return b''.join(state[i].to_bytes(4, 'little') for i in range(16)) + +class FSChaCha20: + """Rekeying wrapper stream cipher around ChaCha20.""" + def __init__(self, initial_key, rekey_interval=REKEY_INTERVAL): + self._key = initial_key + self._rekey_interval = rekey_interval + self._block_counter = 0 + self._chunk_counter = 0 + self._keystream = b'' + + def _get_keystream_bytes(self, nbytes): + while len(self._keystream) < nbytes: + nonce = ((0).to_bytes(4, 'little') + (self._chunk_counter // self._rekey_interval).to_bytes(8, 'little')) + self._keystream += chacha20_block(self._key, nonce, self._block_counter) + self._block_counter += 1 + ret = self._keystream[:nbytes] + self._keystream = self._keystream[nbytes:] + return ret + + def crypt(self, chunk): + ks = self._get_keystream_bytes(len(chunk)) + ret = bytes([ks[i] ^ chunk[i] for i in range(len(chunk))]) + if ((self._chunk_counter + 1) % self._rekey_interval) == 0: + self._key = self._get_keystream_bytes(32) + self._block_counter = 0 + self._keystream = b'' + self._chunk_counter += 1 + return ret + + +# Test vectors from RFC7539/8439 consisting of 32 byte key, 12 byte nonce, block counter +# and 64 byte output after applying `chacha20_block` function +CHACHA20_TESTS = [ + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0x09000000, 0x4a000000], 1, + "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4e" + "d2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 1, + "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" + "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"], + ["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 1, + "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a" + "8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0"], + ["00ff000000000000000000000000000000000000000000000000000000000000", [0, 0], 2, + "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca" + "13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0x200000000000000], 0, + "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7" + "8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x4a000000], 1, + "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf78" + "8a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b7"], + ["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 0, + "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41" + "bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 1], 0, + "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32" + "111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x0706050403020100], 0, + "f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c1" + "34a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a"], +] + +FSCHACHA20_TESTS = [ + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0000000000000000000000000000000000000000000000000000000000000000", 256, + "a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349"], + ["01", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, "ea"], + ["e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a", + "8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a", 4096, + "8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9"], +] + + +class TestFrameworkChacha(unittest.TestCase): + def test_chacha20(self): + """ChaCha20 test vectors.""" + for test_vector in CHACHA20_TESTS: + hex_key, nonce, counter, hex_output = test_vector + key = bytes.fromhex(hex_key) + nonce_bytes = nonce[0].to_bytes(4, 'little') + nonce[1].to_bytes(8, 'little') + keystream = chacha20_block(key, nonce_bytes, counter) + self.assertEqual(hex_output, keystream.hex()) + + def test_fschacha20(self): + """FSChaCha20 test vectors.""" + for test_vector in FSCHACHA20_TESTS: + hex_plaintext, hex_key, rekey_interval, hex_ciphertext_after_rotation = test_vector + plaintext = bytes.fromhex(hex_plaintext) + key = bytes.fromhex(hex_key) + fsc20 = FSChaCha20(key, rekey_interval) + for _ in range(rekey_interval): + fsc20.crypt(plaintext) + + ciphertext = fsc20.crypt(plaintext) + self.assertEqual(hex_ciphertext_after_rotation, ciphertext.hex()) diff --git a/test/functional/test_framework/ellswift.py b/test/functional/test_framework/crypto/ellswift.py index 97b10118e6..429b7b9f4d 100644 --- a/test/functional/test_framework/ellswift.py +++ b/test/functional/test_framework/crypto/ellswift.py @@ -12,7 +12,7 @@ import os import random import unittest -from test_framework.secp256k1 import FE, G, GE +from test_framework.crypto.secp256k1 import FE, G, GE # Precomputed constant square root of -3 (mod p). MINUS_3_SQRT = FE(-3).sqrt() diff --git a/test/functional/test_framework/ellswift_decode_test_vectors.csv b/test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv index 1bab96b721..1bab96b721 100644 --- a/test/functional/test_framework/ellswift_decode_test_vectors.csv +++ b/test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv diff --git a/test/functional/test_framework/crypto/hkdf.py b/test/functional/test_framework/crypto/hkdf.py new file mode 100644 index 0000000000..7e8958733c --- /dev/null +++ b/test/functional/test_framework/crypto/hkdf.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only HKDF-SHA256 implementation + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import hashlib +import hmac + + +def hmac_sha256(key, data): + """Compute HMAC-SHA256 from specified byte arrays key and data.""" + return hmac.new(key, data, hashlib.sha256).digest() + + +def hkdf_sha256(length, ikm, salt, info): + """Derive a key using HKDF-SHA256.""" + if len(salt) == 0: + salt = bytes([0] * 32) + prk = hmac_sha256(salt, ikm) + t = b"" + okm = b"" + for i in range((length + 32 - 1) // 32): + t = hmac_sha256(prk, t + info + bytes([i + 1])) + okm += t + return okm[:length] diff --git a/test/functional/test_framework/crypto/muhash.py b/test/functional/test_framework/crypto/muhash.py new file mode 100644 index 0000000000..09241f6203 --- /dev/null +++ b/test/functional/test_framework/crypto/muhash.py @@ -0,0 +1,55 @@ +# Copyright (c) 2020 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Native Python MuHash3072 implementation.""" + +import hashlib +import unittest + +from .chacha20 import chacha20_block + +def data_to_num3072(data): + """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations.""" + bytes384 = b"" + for counter in range(6): + bytes384 += chacha20_block(data, bytes(12), counter) + return int.from_bytes(bytes384, 'little') + +class MuHash3072: + """Class representing the MuHash3072 computation of a set. + + See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html + """ + + MODULUS = 2**3072 - 1103717 + + def __init__(self): + """Initialize for an empty set.""" + self.numerator = 1 + self.denominator = 1 + + def insert(self, data): + """Insert a byte array data in the set.""" + data_hash = hashlib.sha256(data).digest() + self.numerator = (self.numerator * data_to_num3072(data_hash)) % self.MODULUS + + def remove(self, data): + """Remove a byte array from the set.""" + data_hash = hashlib.sha256(data).digest() + self.denominator = (self.denominator * data_to_num3072(data_hash)) % self.MODULUS + + def digest(self): + """Extract the final hash. Does not modify this object.""" + val = (self.numerator * pow(self.denominator, -1, self.MODULUS)) % self.MODULUS + bytes384 = val.to_bytes(384, 'little') + return hashlib.sha256(bytes384).digest() + +class TestFrameworkMuhash(unittest.TestCase): + def test_muhash(self): + muhash = MuHash3072() + muhash.insert(b'\x00' * 32) + muhash.insert((b'\x01' + b'\x00' * 31)) + muhash.remove((b'\x02' + b'\x00' * 31)) + finalized = muhash.digest() + # This mirrors the result in the C++ MuHash3072 unit test + self.assertEqual(finalized[::-1].hex(), "10d312b100cbd32ada024a6646e40d3482fcff103668d2625f10002a607d5863") diff --git a/test/functional/test_framework/crypto/poly1305.py b/test/functional/test_framework/crypto/poly1305.py new file mode 100644 index 0000000000..967b90254d --- /dev/null +++ b/test/functional/test_framework/crypto/poly1305.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of Poly1305 authenticator + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + + +class Poly1305: + """Class representing a running poly1305 computation.""" + MODULUS = 2**130 - 5 + + def __init__(self, key): + self.r = int.from_bytes(key[:16], 'little') & 0xffffffc0ffffffc0ffffffc0fffffff + self.s = int.from_bytes(key[16:], 'little') + + def tag(self, data): + """Compute the poly1305 tag.""" + acc, length = 0, len(data) + for i in range((length + 15) // 16): + chunk = data[i * 16:min(length, (i + 1) * 16)] + val = int.from_bytes(chunk, 'little') + 256**len(chunk) + acc = (self.r * (acc + val)) % Poly1305.MODULUS + return ((acc + self.s) & 0xffffffffffffffffffffffffffffffff).to_bytes(16, 'little') + + +# Test vectors from RFC7539/8439 consisting of message to be authenticated, 32 byte key and computed 16 byte tag +POLY1305_TESTS = [ + # RFC 7539, section 2.5.2. + ["43727970746f6772617068696320466f72756d2052657365617263682047726f7570", + "85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b", + "a8061dc1305136c6c22b8baf0c0127a9"], + # RFC 7539, section A.3. + ["00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000"], + ["416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e747269627" + "5746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465" + "726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686" + "520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e2022494554" + "4620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c20737461746" + "56d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c65" + "6374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207" + "768696368206172652061646472657373656420746f", + "0000000000000000000000000000000036e5f6b5c5e06070f0efca96227a863e", + "36e5f6b5c5e06070f0efca96227a863e"], + ["416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e747269627" + "5746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465" + "726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686" + "520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e2022494554" + "4620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c20737461746" + "56d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c65" + "6374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207" + "768696368206172652061646472657373656420746f", + "36e5f6b5c5e06070f0efca96227a863e00000000000000000000000000000000", + "f3477e7cd95417af89a6b8794c310cf0"], + ["2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e6420676" + "96d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e" + "6420746865206d6f6d65207261746873206f757467726162652e", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + "4541669a7eaaee61e708dc7cbcc5eb62"], + ["ffffffffffffffffffffffffffffffff", + "0200000000000000000000000000000000000000000000000000000000000000", + "03000000000000000000000000000000"], + ["02000000000000000000000000000000", + "02000000000000000000000000000000ffffffffffffffffffffffffffffffff", + "03000000000000000000000000000000"], + ["fffffffffffffffffffffffffffffffff0ffffffffffffffffffffffffffffff11000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000", + "05000000000000000000000000000000"], + ["fffffffffffffffffffffffffffffffffbfefefefefefefefefefefefefefefe01010101010101010101010101010101", + "0100000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000"], + ["fdffffffffffffffffffffffffffffff", + "0200000000000000000000000000000000000000000000000000000000000000", + "faffffffffffffffffffffffffffffff"], + ["e33594d7505e43b900000000000000003394d7505e4379cd01000000000000000000000000000000000000000000000001000000000000000000000000000000", + "0100000000000000040000000000000000000000000000000000000000000000", + "14000000000000005500000000000000"], + ["e33594d7505e43b900000000000000003394d7505e4379cd010000000000000000000000000000000000000000000000", + "0100000000000000040000000000000000000000000000000000000000000000", + "13000000000000000000000000000000"], +] + + +class TestFrameworkPoly1305(unittest.TestCase): + def test_poly1305(self): + """Poly1305 test vectors.""" + for test_vector in POLY1305_TESTS: + hex_message, hex_key, hex_tag = test_vector + message = bytes.fromhex(hex_message) + key = bytes.fromhex(hex_key) + tag = bytes.fromhex(hex_tag) + comp_tag = Poly1305(key).tag(message) + self.assertEqual(tag, comp_tag) diff --git a/test/functional/test_framework/ripemd160.py b/test/functional/test_framework/crypto/ripemd160.py index 12801364b4..12801364b4 100644 --- a/test/functional/test_framework/ripemd160.py +++ b/test/functional/test_framework/crypto/ripemd160.py diff --git a/test/functional/test_framework/secp256k1.py b/test/functional/test_framework/crypto/secp256k1.py index 2e9e419da5..2e9e419da5 100644 --- a/test/functional/test_framework/secp256k1.py +++ b/test/functional/test_framework/crypto/secp256k1.py diff --git a/test/functional/test_framework/siphash.py b/test/functional/test_framework/crypto/siphash.py index bd13b2c948..bd13b2c948 100644 --- a/test/functional/test_framework/siphash.py +++ b/test/functional/test_framework/crypto/siphash.py diff --git a/test/functional/test_framework/xswiftec_inv_test_vectors.csv b/test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv index 138c4cf85c..138c4cf85c 100644 --- a/test/functional/test_framework/xswiftec_inv_test_vectors.csv +++ b/test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 6c1892539f..06252f8996 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -13,7 +13,7 @@ import os import random import unittest -from test_framework import secp256k1 +from test_framework.crypto import secp256k1 # Point with no known discrete log. H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 8f3aea8785..d008cb39aa 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -29,7 +29,7 @@ import struct import time import unittest -from test_framework.siphash import siphash256 +from test_framework.crypto.siphash import siphash256 from test_framework.util import assert_equal MAX_LOCATOR_SZ = 101 diff --git a/test/functional/test_framework/muhash.py b/test/functional/test_framework/muhash.py deleted file mode 100644 index 0d96114e3e..0000000000 --- a/test/functional/test_framework/muhash.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2020 Pieter Wuille -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Native Python MuHash3072 implementation.""" - -import hashlib -import unittest - -def rot32(v, bits): - """Rotate the 32-bit value v left by bits bits.""" - bits %= 32 # Make sure the term below does not throw an exception - return ((v << bits) & 0xffffffff) | (v >> (32 - bits)) - -def chacha20_doubleround(s): - """Apply a ChaCha20 double round to 16-element state array s. - - See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439 - """ - QUARTER_ROUNDS = [(0, 4, 8, 12), - (1, 5, 9, 13), - (2, 6, 10, 14), - (3, 7, 11, 15), - (0, 5, 10, 15), - (1, 6, 11, 12), - (2, 7, 8, 13), - (3, 4, 9, 14)] - - for a, b, c, d in QUARTER_ROUNDS: - s[a] = (s[a] + s[b]) & 0xffffffff - s[d] = rot32(s[d] ^ s[a], 16) - s[c] = (s[c] + s[d]) & 0xffffffff - s[b] = rot32(s[b] ^ s[c], 12) - s[a] = (s[a] + s[b]) & 0xffffffff - s[d] = rot32(s[d] ^ s[a], 8) - s[c] = (s[c] + s[d]) & 0xffffffff - s[b] = rot32(s[b] ^ s[c], 7) - -def chacha20_32_to_384(key32): - """Specialized ChaCha20 implementation with 32-byte key, 0 IV, 384-byte output.""" - # See RFC 8439 section 2.3 for chacha20 parameters - CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] - - key_bytes = [0]*8 - for i in range(8): - key_bytes[i] = int.from_bytes(key32[(4 * i):(4 * (i+1))], 'little') - - INITIALIZATION_VECTOR = [0] * 4 - init = CONSTANTS + key_bytes + INITIALIZATION_VECTOR - out = bytearray() - for counter in range(6): - init[12] = counter - s = init.copy() - for _ in range(10): - chacha20_doubleround(s) - for i in range(16): - out.extend(((s[i] + init[i]) & 0xffffffff).to_bytes(4, 'little')) - return bytes(out) - -def data_to_num3072(data): - """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations.""" - bytes384 = chacha20_32_to_384(data) - return int.from_bytes(bytes384, 'little') - -class MuHash3072: - """Class representing the MuHash3072 computation of a set. - - See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html - """ - - MODULUS = 2**3072 - 1103717 - - def __init__(self): - """Initialize for an empty set.""" - self.numerator = 1 - self.denominator = 1 - - def insert(self, data): - """Insert a byte array data in the set.""" - data_hash = hashlib.sha256(data).digest() - self.numerator = (self.numerator * data_to_num3072(data_hash)) % self.MODULUS - - def remove(self, data): - """Remove a byte array from the set.""" - data_hash = hashlib.sha256(data).digest() - self.denominator = (self.denominator * data_to_num3072(data_hash)) % self.MODULUS - - def digest(self): - """Extract the final hash. Does not modify this object.""" - val = (self.numerator * pow(self.denominator, -1, self.MODULUS)) % self.MODULUS - bytes384 = val.to_bytes(384, 'little') - return hashlib.sha256(bytes384).digest() - -class TestFrameworkMuhash(unittest.TestCase): - def test_muhash(self): - muhash = MuHash3072() - muhash.insert(b'\x00' * 32) - muhash.insert((b'\x01' + b'\x00' * 31)) - muhash.remove((b'\x02' + b'\x00' * 31)) - finalized = muhash.digest() - # This mirrors the result in the C++ MuHash3072 unit test - self.assertEqual(finalized[::-1].hex(), "10d312b100cbd32ada024a6646e40d3482fcff103668d2625f10002a607d5863") - - def test_chacha20(self): - def chacha_check(key, result): - self.assertEqual(chacha20_32_to_384(key)[:64].hex(), result) - - # Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 - # Since the nonce is hardcoded to 0 in our function we only use those vectors. - chacha_check([0]*32, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586") - chacha_check([0]*31 + [1], "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963") diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index be4ed624fc..b1ed97b794 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -364,8 +364,8 @@ class P2PInterface(P2PConnection): vt.addrFrom.port = 0 self.on_connection_send_msg = vt # Will be sent in connection_made callback - def peer_connect(self, *args, services=P2P_SERVICES, send_version=True, **kwargs): - create_conn = super().peer_connect(*args, **kwargs) + def peer_connect(self, *, services=P2P_SERVICES, send_version, **kwargs): + create_conn = super().peer_connect(**kwargs) if send_version: self.peer_connect_send_version(services) @@ -456,7 +456,8 @@ class P2PInterface(P2PConnection): self.send_message(msg_verack()) self.nServices = message.nServices self.relay = message.relay - self.send_message(msg_getaddr()) + if self.p2p_connected_to_node: + self.send_message(msg_getaddr()) # Connection helper methods diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 17a954cb22..78d8580794 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -10,7 +10,6 @@ This file is modified from python-bitcoinlib. from collections import namedtuple import struct import unittest -from typing import List, Dict from .key import TaggedHash, tweak_add_pubkey, compute_xonly_pubkey @@ -24,7 +23,7 @@ from .messages import ( uint256_from_str, ) -from .ripemd160 import ripemd160 +from .crypto.ripemd160 import ripemd160 MAX_SCRIPT_ELEMENT_SIZE = 520 MAX_PUBKEYS_PER_MULTI_A = 999 @@ -110,8 +109,8 @@ class CScriptOp(int): _opcode_instances.append(super().__new__(cls, n)) return _opcode_instances[n] -OPCODE_NAMES: Dict[CScriptOp, str] = {} -_opcode_instances: List[CScriptOp] = [] +OPCODE_NAMES: dict[CScriptOp, str] = {} +_opcode_instances: list[CScriptOp] = [] # Populate opcode instance table for n in range(0xff + 1): diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 4e6d245b5f..0ee332b75b 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -19,7 +19,6 @@ import sys import tempfile import time -from typing import List from .address import create_deterministic_address_bcrt1_p2tr_op_true from .authproxy import JSONRPCException from . import coverage @@ -30,6 +29,7 @@ from .util import ( PortSeed, assert_equal, check_json_precision, + find_vout_for_address, get_datadir_path, initialize_datadir, p2p_port, @@ -96,7 +96,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.chain: str = 'regtest' self.setup_clean_chain: bool = False - self.nodes: List[TestNode] = [] + self.nodes: list[TestNode] = [] self.extra_args = None self.network_thread = None self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond @@ -507,8 +507,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): assert_equal(len(binary_cli), num_nodes) for i in range(num_nodes): args = list(extra_args[i]) - if self.options.v2transport and ("-v2transport=0" not in args): - args.append("-v2transport=1") test_node_i = TestNode( i, get_datadir_path(self.options.tmpdir, i), @@ -527,6 +525,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): start_perf=self.options.perf, use_valgrind=self.options.valgrind, descriptors=self.options.descriptors, + v2transport=self.options.v2transport, ) self.nodes.append(test_node_i) if not test_node_i.version_is_at_least(170000): @@ -601,7 +600,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): ip_port = "127.0.0.1:" + str(p2p_port(b)) if peer_advertises_v2 is None: - peer_advertises_v2 = self.options.v2transport + peer_advertises_v2 = from_connection.use_v2transport if peer_advertises_v2: from_connection.addnode(node=ip_port, command="onetry", v2transport=True) @@ -697,6 +696,22 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): sync_fun() if sync_fun else self.sync_all() return blocks + def create_outpoints(self, node, *, outputs): + """Send funds to a given list of `{address: amount}` targets using the bitcoind + wallet and return the corresponding outpoints as a list of dictionaries + `[{"txid": txid, "vout": vout1}, {"txid": txid, "vout": vout2}, ...]`. + The result can be used to specify inputs for RPCs like `createrawtransaction`, + `createpsbt`, `lockunspent` etc.""" + assert all(len(output.keys()) == 1 for output in outputs) + send_res = node.send(outputs) + assert send_res["complete"] + utxos = [] + for output in outputs: + address = list(output.keys())[0] + vout = find_vout_for_address(node, send_res["txid"], address) + utxos.append({"txid": send_res["txid"], "vout": vout}) + return utxos + def sync_blocks(self, nodes=None, wait=1, timeout=60): """ Wait until everybody has the same tip. diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index c77cfbdd91..b3f777b9df 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -67,7 +67,7 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False): + def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -126,6 +126,12 @@ class TestNode(): if self.version_is_at_least(239000): self.args.append("-loglevel=trace") + # Default behavior from global -v2transport flag is added to args to persist it over restarts. + # May be overwritten in individual tests, using extra_args. + self.default_to_v2 = v2transport + if self.default_to_v2: + self.args.append("-v2transport=1") + self.cli = TestNodeCLI(bitcoin_cli, self.datadir_path) self.use_cli = use_cli self.start_perf = start_perf @@ -198,6 +204,8 @@ class TestNode(): if extra_args is None: extra_args = self.extra_args + self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args) + # Add a new stdout and stderr file each time bitcoind is started if stderr is None: stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) @@ -252,7 +260,7 @@ class TestNode(): if self.version_is_at_least(190000): # getmempoolinfo.loaded is available since commit # bb8ae2c (version 0.19.0) - wait_until_helper_internal(lambda: rpc.getmempoolinfo()['loaded'], timeout_factor=self.timeout_factor) + self.wait_until(lambda: rpc.getmempoolinfo()['loaded']) # 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 @@ -406,7 +414,7 @@ class TestNode(): 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_internal(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor) + self.wait_until(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout) def replace_in_config(self, replacements): """ @@ -512,6 +520,23 @@ class TestNode(): str(expected_msgs), print_log)) @contextlib.contextmanager + def wait_for_new_peer(self, timeout=5): + """ + Wait until the node is connected to at least one new peer. We detect this + by watching for an increased highest peer id, using the `getpeerinfo` RPC call. + Note that the simpler approach of only accounting for the number of peers + suffers from race conditions, as disconnects from unrelated previous peers + could happen anytime in-between. + """ + def get_highest_peer_id(): + peer_info = self.getpeerinfo() + return peer_info[-1]["id"] if peer_info else -1 + + initial_peer_id = get_highest_peer_id() + yield + self.wait_until(lambda: get_highest_peer_id() > initial_peer_id, timeout=timeout) + + @contextlib.contextmanager def profile_with_perf(self, profile_name: str): """ Context manager that allows easy profiling of node activity using `perf`. @@ -634,7 +659,7 @@ class TestNode(): assert_msg += "with expected error " + expected_msg self._raise_assertion_error(assert_msg) - def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs): + def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, send_version=True, **kwargs): """Add an inbound p2p connection to the node. This method adds the p2p connection to the self.p2ps list and also @@ -644,9 +669,12 @@ class TestNode(): if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' - p2p_conn.peer_connect(**kwargs, net=self.chain, timeout_factor=self.timeout_factor)() + p2p_conn.p2p_connected_to_node = True + p2p_conn.peer_connect(**kwargs, send_version=send_version, net=self.chain, timeout_factor=self.timeout_factor)() self.p2ps.append(p2p_conn) p2p_conn.wait_until(lambda: p2p_conn.is_connected, check_connected=False) + if send_version: + p2p_conn.wait_until(lambda: not p2p_conn.on_connection_send_msg) if wait_for_verack: # Wait for the node to send us the version and verack p2p_conn.wait_for_verack() @@ -689,6 +717,7 @@ class TestNode(): self.log.debug("Connecting to %s:%d %s" % (address, port, connection_type)) self.addconnection('%s:%d' % (address, port), connection_type) + p2p_conn.p2p_connected_to_node = False p2p_conn.peer_accept_connection(connect_cb=addconnection_callback, connect_id=p2p_idx + 1, net=self.chain, timeout_factor=self.timeout_factor, **kwargs)() if connection_type == "feeler": @@ -699,6 +728,7 @@ class TestNode(): p2p_conn.wait_for_connect() self.p2ps.append(p2p_conn) + p2p_conn.wait_until(lambda: not p2p_conn.on_connection_send_msg) if wait_for_verack: p2p_conn.wait_for_verack() p2p_conn.sync_with_ping() @@ -716,7 +746,7 @@ class TestNode(): p.peer_disconnect() del self.p2ps[:] - wait_until_helper_internal(lambda: self.num_test_p2p_connections() == 0, timeout_factor=self.timeout_factor) + self.wait_until(lambda: self.num_test_p2p_connections() == 0) def bumpmocktime(self, seconds): """Fast forward using setmocktime to self.mocktime + seconds. Requires setmocktime to have @@ -725,6 +755,9 @@ class TestNode(): self.mocktime += seconds self.setmocktime(self.mocktime) + def wait_until(self, test_function, timeout=60): + return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.timeout_factor) + class TestNodeCLIAttr: def __init__(self, cli, command): @@ -777,15 +810,15 @@ class TestNodeCLI(): results.append(dict(error=e)) return results - def send_cli(self, command=None, *args, **kwargs): + def send_cli(self, clicommand=None, *args, **kwargs): """Run bitcoin-cli command. Deserializes returned string as python object.""" pos_args = [arg_to_cli(arg) for arg in args] named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()] p_args = [self.binary, f"-datadir={self.datadir}"] + self.options if named_args: p_args += ["-named"] - if command is not None: - p_args += [command] + if clicommand is not None: + p_args += [clicommand] p_args += pos_args + named_args self.log.debug("Running bitcoin-cli {}".format(p_args[2:])) process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 61346e9d19..c65e3e38e6 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -13,14 +13,14 @@ import json import logging import os import pathlib -import random import re import sys import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException -from typing import Callable, Optional, Tuple +from collections.abc import Callable +from typing import Optional logger = logging.getLogger("TestFramework.utils") @@ -287,12 +287,6 @@ def sha256sum_file(filename): return h.digest() -# TODO: Remove and use random.randbytes(n) instead, available in Python 3.9 -def random_bytes(n): - """Return a random bytes object of length n.""" - return bytes(random.getrandbits(8) for i in range(n)) - - # RPC/P2P connection constants and functions ############################################ @@ -416,7 +410,7 @@ def get_datadir_path(dirname, n): return pathlib.Path(dirname) / f"node{n}" -def get_temp_default_datadir(temp_dir: pathlib.Path) -> Tuple[dict, pathlib.Path]: +def get_temp_default_datadir(temp_dir: pathlib.Path) -> tuple[dict, pathlib.Path]: """Return os-specific environment variables that can be set to make the GetDefaultDataDir() function return a datadir path under the provided temp_dir, as well as the complete path it would return.""" @@ -490,18 +484,6 @@ def check_node_connections(*, node, num_in, num_out): ############################# -def find_output(node, txid, amount, *, blockhash=None): - """ - Return index to output of txid with value amount - Raises exception if there is none. - """ - txdata = node.getrawtransaction(txid, 1, blockhash) - for i in range(len(txdata["vout"])): - if txdata["vout"][i]["value"] == amount: - return i - raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount))) - - # Create large OP_RETURN txouts that can be appended to a transaction # to make it large (helper for constructing large transactions). The # total serialized size of the txouts is about 66k vbytes. diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 035a482f4c..53c8e1b0cc 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -9,7 +9,6 @@ from decimal import Decimal from enum import Enum from typing import ( Any, - List, Optional, ) from test_framework.address import ( @@ -284,7 +283,7 @@ class MiniWallet: def create_self_transfer_multi( self, *, - utxos_to_spend: Optional[List[dict]] = None, + utxos_to_spend: Optional[list[dict]] = None, num_outputs=1, amount_per_output=0, locktime=0, diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index fbf48a0e4d..2460b2e3e6 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -73,12 +73,15 @@ TEST_EXIT_SKIPPED = 77 # the output of `git grep unittest.TestCase ./test/functional/test_framework` TEST_FRAMEWORK_MODULES = [ "address", + "crypto.bip324_cipher", "blocktools", - "ellswift", + "crypto.chacha20", + "crypto.ellswift", "key", "messages", - "muhash", - "ripemd160", + "crypto.muhash", + "crypto.poly1305", + "crypto.ripemd160", "script", "segwit_addr", ] @@ -105,7 +108,6 @@ BASE_SCRIPTS = [ 'feature_maxuploadtarget.py', 'mempool_updatefromblock.py', 'mempool_persist.py --descriptors', - 'wallet_miniscript.py --descriptors', # vv Tests less than 60s vv 'rpc_psbt.py --legacy-wallet', 'rpc_psbt.py --descriptors', @@ -149,10 +151,12 @@ BASE_SCRIPTS = [ 'p2p_sendheaders.py', 'wallet_listtransactions.py --legacy-wallet', 'wallet_listtransactions.py --descriptors', + 'wallet_miniscript.py --descriptors', # vv Tests less than 30s vv 'p2p_invalid_messages.py', 'rpc_createmultisig.py', 'p2p_timeouts.py', + 'p2p_timeouts.py --v2transport', 'wallet_dump.py --legacy-wallet', 'rpc_signer.py', 'wallet_signer.py --descriptors', @@ -203,6 +207,8 @@ BASE_SCRIPTS = [ 'wallet_createwallet.py --descriptors', 'wallet_watchonly.py --legacy-wallet', 'wallet_watchonly.py --usecli --legacy-wallet', + 'wallet_reindex.py --legacy-wallet', + 'wallet_reindex.py --descriptors', 'wallet_reorgsrestore.py', 'wallet_conflicts.py --legacy-wallet', 'wallet_conflicts.py --descriptors', @@ -240,6 +246,7 @@ BASE_SCRIPTS = [ 'p2p_getdata.py', 'p2p_addrfetch.py', 'rpc_net.py', + 'rpc_net.py --v2transport', 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', 'wallet_descriptor.py --descriptors', @@ -357,7 +364,6 @@ BASE_SCRIPTS = [ 'rpc_scanblocks.py', 'p2p_sendtxrcncl.py', 'rpc_scantxoutset.py', - 'feature_txindex_compatibility.py', 'feature_unsupported_utxo_db.py', 'feature_logging.py', 'feature_anchors.py', @@ -366,6 +372,7 @@ BASE_SCRIPTS = [ 'wallet_orphanedreward.py', 'wallet_timelock.py', 'p2p_node_network_limited.py', + 'p2p_node_network_limited.py --v2transport', 'p2p_permissions.py', 'feature_blocksdir.py', 'wallet_startup.py', @@ -560,8 +567,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= test_framework_tests.addTest(unittest.TestLoader().loadTestsFromName("test_framework.{}".format(module))) result = unittest.TextTestRunner(verbosity=1, failfast=True).run(test_framework_tests) if not result.wasSuccessful(): - logging.debug("Early exiting after failure in TestFramework unit tests") - sys.exit(False) + sys.exit("Early exiting after failure in TestFramework unit tests") flags = ['--cachedir={}'.format(cache_dir)] + args diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index c257bda452..9d3c55d6b6 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -257,7 +257,7 @@ class AvoidReuseTest(BitcoinTestFramework): if not self.options.descriptors: # For the second send, we transmute it to a related single-key address - # to make sure it's also detected as re-use + # to make sure it's also detected as reuse fund_spk = address_to_scriptpubkey(fundaddr).hex() fund_decoded = self.nodes[0].decodescript(fund_spk) if second_addr_type == "p2sh-segwit": diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 01149a0977..f798eee365 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -18,7 +18,6 @@ from test_framework.util import ( assert_equal, assert_fee_amount, assert_raises_rpc_error, - find_vout_for_address, ) from test_framework.wallet_util import test_address from test_framework.wallet import MiniWallet @@ -471,10 +470,9 @@ class WalletTest(BitcoinTestFramework): # Import address and private key to check correct behavior of spendable unspents # 1. Send some coins to generate new UTXO address_to_import = self.nodes[2].getnewaddress() - txid = self.nodes[0].sendtoaddress(address_to_import, 1) + utxo = self.create_outpoints(self.nodes[0], outputs=[{address_to_import: 1}])[0] self.sync_mempools(self.nodes[0:3]) - vout = find_vout_for_address(self.nodes[2], txid, address_to_import) - self.nodes[2].lockunspent(False, [{"txid": txid, "vout": vout}]) + self.nodes[2].lockunspent(False, [utxo]) self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_all(self.nodes[0:3])) self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)") @@ -641,7 +639,7 @@ class WalletTest(BitcoinTestFramework): node0_balance = self.nodes[0].getbalance() # With walletrejectlongchains we will not create the tx and store it in our wallet. - assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01')) + assert_raises_rpc_error(-6, f"too many unconfirmed ancestors [limit: {chainlimit * 2}]", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01')) # Verify nothing new in wallet assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) diff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py index 2f9c924e71..1315bccafd 100755 --- a/test/functional/wallet_fast_rescan.py +++ b/test/functional/wallet_fast_rescan.py @@ -4,8 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that fast rescan using block filters for descriptor wallets detects top-ups correctly and finds the same transactions than the slow variant.""" -from typing import List - from test_framework.address import address_to_scriptpubkey from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework @@ -32,7 +30,7 @@ class WalletFastRescanTest(BitcoinTestFramework): self.skip_if_no_wallet() self.skip_if_no_sqlite() - def get_wallet_txids(self, node: TestNode, wallet_name: str) -> List[str]: + def get_wallet_txids(self, node: TestNode, wallet_name: str) -> list[str]: w = node.get_wallet_rpc(wallet_name) txs = w.listtransactions('*', 1000000) return [tx['txid'] for tx in txs] diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 77611649ac..a331ba997e 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -22,7 +22,6 @@ from test_framework.util import ( assert_greater_than_or_equal, assert_raises_rpc_error, count_bytes, - find_vout_for_address, get_fee, ) from test_framework.wallet_util import generate_keypair, WalletUnlock @@ -85,14 +84,13 @@ class RawTransactionsTest(BitcoinTestFramework): Unlock all UTXOs except the watchonly one """ to_keep = [] - if self.watchonly_txid is not None and self.watchonly_vout is not None: - to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout}) + if self.watchonly_utxo is not None: + to_keep.append(self.watchonly_utxo) wallet.lockunspent(True) wallet.lockunspent(False, to_keep) def run_test(self): - self.watchonly_txid = None - self.watchonly_vout = None + self.watchonly_utxo = None self.log.info("Connect nodes, set fees, generate blocks, and sync") self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] # This test is not meant to test fee estimation and we'd like @@ -163,11 +161,10 @@ class RawTransactionsTest(BitcoinTestFramework): watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"] self.watchonly_amount = Decimal(200) wwatch.importpubkey(watchonly_pubkey, "", True) - self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.watchonly_amount) + self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0] # Lock UTXO so nodes[0] doesn't accidentally spend it - self.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address) - self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) + self.nodes[0].lockunspent(False, [self.watchonly_utxo]) self.nodes[0].sendtoaddress(self.nodes[3].get_wallet_rpc(self.default_wallet_name).getnewaddress(), self.watchonly_amount / 10) @@ -738,7 +735,7 @@ class RawTransactionsTest(BitcoinTestFramework): result = wwatch.fundrawtransaction(rawtx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) - assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid) + assert_equal(res_dec["vin"][0]["txid"], self.watchonly_utxo['txid']) assert "fee" in result.keys() assert_greater_than(result["changepos"], -1) @@ -758,7 +755,7 @@ class RawTransactionsTest(BitcoinTestFramework): result = wwatch.fundrawtransaction(rawtx, includeWatching=True, changeAddress=w3.getrawchangeaddress(), subtractFeeFromOutputs=[0]) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) - assert res_dec["vin"][0]["txid"] == self.watchonly_txid + assert res_dec["vin"][0]["txid"] == self.watchonly_utxo['txid'] assert_greater_than(result["fee"], 0) assert_equal(result["changepos"], -1) @@ -970,10 +967,9 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn subtract fee from outputs with preset inputs that are sufficient") addr = self.nodes[0].getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 10) - vout = find_vout_for_address(self.nodes[0], txid, addr) + utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 10}])[0] - rawtx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 5}]) + rawtx = self.nodes[0].createrawtransaction([utxo], [{self.nodes[0].getnewaddress(): 5}]) fundedtx = self.nodes[0].fundrawtransaction(rawtx, subtractFeeFromOutputs=[0]) signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx['hex']) self.nodes[0].sendrawtransaction(signedtx['hex']) @@ -1264,14 +1260,12 @@ class RawTransactionsTest(BitcoinTestFramework): addr = wallet.getnewaddress(address_type="bech32") ext_addr = self.nodes[0].getnewaddress(address_type="bech32") - txid = self.nodes[0].send([{addr: 5}, {ext_addr: 5}])["txid"] - vout = find_vout_for_address(self.nodes[0], txid, addr) - ext_vout = find_vout_for_address(self.nodes[0], txid, ext_addr) + utxo, ext_utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 5}, {ext_addr: 5}]) self.nodes[0].sendtoaddress(wallet.getnewaddress(address_type="bech32"), 5) self.generate(self.nodes[0], 1) - rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}]) + rawtx = wallet.createrawtransaction([utxo], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}]) fundedtx = wallet.fundrawtransaction(rawtx, fee_rate=10, change_type="bech32") # with 71-byte signatures we should expect following tx size # tx overhead (10) + 2 inputs (41 each) + 2 p2wpkh (31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 byte sig witnesses (107 each)) / witness scaling factor (4) @@ -1279,7 +1273,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fundedtx['fee'] * COIN, tx_size * 10) # Using the other output should have 72 byte sigs - rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': ext_vout}], [{self.nodes[0].getnewaddress(): 13}]) + rawtx = wallet.createrawtransaction([ext_utxo], [{self.nodes[0].getnewaddress(): 13}]) ext_desc = self.nodes[0].getaddressinfo(ext_addr)["desc"] fundedtx = wallet.fundrawtransaction(rawtx, fee_rate=10, change_type="bech32", solving_data={"descriptors": [ext_desc]}) # tx overhead (10) + 3 inputs (41 each) + 2 p2wpkh(31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 bytes sig witnesses (107 each) + p2wpkh 72 byte sig witness (108)) / witness scaling factor (4) @@ -1298,10 +1292,9 @@ class RawTransactionsTest(BitcoinTestFramework): addr = wallet.getnewaddress() inputs = [] for i in range(0, 2): - txid = self.nodes[2].sendtoaddress(addr, 5) - self.sync_mempools() - vout = find_vout_for_address(wallet, txid, addr) - inputs.append((txid, vout)) + utxo = self.create_outpoints(self.nodes[2], outputs=[{addr: 5}])[0] + inputs.append((utxo['txid'], utxo['vout'])) + self.sync_mempools() # Unsafe inputs are ignored by default. rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}]) diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index ad5ae111aa..1f1f92589c 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -24,7 +24,6 @@ from test_framework.descriptors import descsum_create from test_framework.util import ( assert_equal, assert_raises_rpc_error, - find_vout_for_address, ) from test_framework.wallet_util import ( get_generate_key, @@ -493,12 +492,10 @@ class ImportDescriptorsTest(BitcoinTestFramework): assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) # generate some utxos for next tests - txid = w0.sendtoaddress(addr, 10) - vout = find_vout_for_address(self.nodes[0], txid, addr) + utxo = self.create_outpoints(w0, outputs=[{addr: 10}])[0] addr2 = wmulti_pub.getnewaddress('', 'bech32') - txid2 = w0.sendtoaddress(addr2, 10) - vout2 = find_vout_for_address(self.nodes[0], txid2, addr2) + utxo2 = self.create_outpoints(w0, outputs=[{addr2: 10}])[0] self.generate(self.nodes[0], 6) assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance()) @@ -554,7 +551,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): assert_equal(res[1]['success'], True) assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors') - rawtx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9.999}) + rawtx = self.nodes[1].createrawtransaction([utxo], {w0.getnewaddress(): 9.999}) tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx) assert_equal(tx_signed_1['complete'], False) tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1['hex']) @@ -648,7 +645,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): }]) assert_equal(res[0]['success'], True) - rawtx = self.nodes[1].createrawtransaction([{'txid': txid2, 'vout': vout2}], {w0.getnewaddress(): 9.999}) + rawtx = self.nodes[1].createrawtransaction([utxo2], {w0.getnewaddress(): 9.999}) tx = wmulti_priv3.signrawtransactionwithwallet(rawtx) assert_equal(tx['complete'], True) self.nodes[1].sendrawtransaction(tx['hex']) diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 18bb8a0cd8..064ce12108 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -111,6 +111,7 @@ class ListTransactionsTest(BitcoinTestFramework): self.run_rbf_opt_in_test() self.run_externally_generated_address_test() + self.run_coinjoin_test() self.run_invalid_parameters_test() self.test_op_return() @@ -281,6 +282,34 @@ class ListTransactionsTest(BitcoinTestFramework): assert_equal(['pizza2'], self.nodes[0].getaddressinfo(addr2)['labels']) assert_equal(['pizza3'], self.nodes[0].getaddressinfo(addr3)['labels']) + def run_coinjoin_test(self): + self.log.info('Check "coin-join" transaction') + input_0 = next(i for i in self.nodes[0].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False)) + input_1 = next(i for i in self.nodes[1].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False)) + raw_hex = self.nodes[0].createrawtransaction( + inputs=[ + { + "txid": input_0["txid"], + "vout": input_0["vout"], + }, + { + "txid": input_1["txid"], + "vout": input_1["vout"], + }, + ], + outputs={ + self.nodes[0].getnewaddress(): 0.123, + self.nodes[1].getnewaddress(): 0.123, + }, + ) + raw_hex = self.nodes[0].signrawtransactionwithwallet(raw_hex)["hex"] + raw_hex = self.nodes[1].signrawtransactionwithwallet(raw_hex)["hex"] + txid_join = self.nodes[0].sendrawtransaction(hexstring=raw_hex, maxfeerate=0) + fee_join = self.nodes[0].getmempoolentry(txid_join)["fees"]["base"] + # Fee should be correct: assert_equal(fee_join, self.nodes[0].gettransaction(txid_join)['fee']) + # But it is not, see for example https://github.com/bitcoin/bitcoin/issues/14136: + assert fee_join != self.nodes[0].gettransaction(txid_join)["fee"] + def run_invalid_parameters_test(self): self.log.info("Test listtransactions RPC parameter validity") assert_raises_rpc_error(-8, 'Label argument must be a valid label name or "*".', self.nodes[0].listtransactions, label="") diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index aede9281d5..e2edaef4da 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -23,7 +23,6 @@ from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_scrip from test_framework.util import ( assert_equal, assert_raises_rpc_error, - find_vout_for_address, sha256sum_file, ) from test_framework.wallet_util import ( @@ -310,14 +309,14 @@ class WalletMigrationTest(BitcoinTestFramework): # Received watchonly tx that is then spent import_sent_addr = default.getnewaddress() imports0.importaddress(import_sent_addr) - received_sent_watchonly_txid = default.sendtoaddress(import_sent_addr, 10) - received_sent_watchonly_vout = find_vout_for_address(self.nodes[0], received_sent_watchonly_txid, import_sent_addr) - send = default.sendall(recipients=[default.getnewaddress()], inputs=[{"txid": received_sent_watchonly_txid, "vout": received_sent_watchonly_vout}]) + received_sent_watchonly_utxo = self.create_outpoints(node=default, outputs=[{import_sent_addr: 10}])[0] + + send = default.sendall(recipients=[default.getnewaddress()], inputs=[received_sent_watchonly_utxo]) sent_watchonly_txid = send["txid"] self.generate(self.nodes[0], 1) received_watchonly_tx_info = imports0.gettransaction(received_watchonly_txid, True) - received_sent_watchonly_tx_info = imports0.gettransaction(received_sent_watchonly_txid, True) + received_sent_watchonly_tx_info = imports0.gettransaction(received_sent_watchonly_utxo["txid"], True) balances = imports0.getbalances() spendable_bal = balances["mine"]["trusted"] @@ -332,7 +331,7 @@ class WalletMigrationTest(BitcoinTestFramework): assert_equal(imports0.getwalletinfo()["descriptors"], True) self.assert_is_sqlite("imports0") assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_watchonly_txid) - assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_txid) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_utxo['txid']) assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, sent_watchonly_txid) assert_equal(len(imports0.listtransactions(include_watchonly=True)), 1) imports0.gettransaction(received_txid) @@ -347,7 +346,7 @@ class WalletMigrationTest(BitcoinTestFramework): received_migrated_watchonly_tx_info = watchonly.gettransaction(received_watchonly_txid) assert_equal(received_watchonly_tx_info["time"], received_migrated_watchonly_tx_info["time"]) assert_equal(received_watchonly_tx_info["timereceived"], received_migrated_watchonly_tx_info["timereceived"]) - received_sent_migrated_watchonly_tx_info = watchonly.gettransaction(received_sent_watchonly_txid) + received_sent_migrated_watchonly_tx_info = watchonly.gettransaction(received_sent_watchonly_utxo["txid"]) assert_equal(received_sent_watchonly_tx_info["time"], received_sent_migrated_watchonly_tx_info["time"]) assert_equal(received_sent_watchonly_tx_info["timereceived"], received_sent_migrated_watchonly_tx_info["timereceived"]) watchonly.gettransaction(sent_watchonly_txid) diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py index d174b525b3..67e1283902 100755 --- a/test/functional/wallet_miniscript.py +++ b/test/functional/wallet_miniscript.py @@ -205,10 +205,10 @@ DESCS_PRIV = [ class WalletMiniscriptTest(BitcoinTestFramework): def add_options(self, parser): self.add_wallet_options(parser, legacy=False) - self.rpc_timeout = 480 def set_test_params(self): self.num_nodes = 1 + self.rpc_timeout = 180 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py new file mode 100755 index 0000000000..5388de4b71 --- /dev/null +++ b/test/functional/wallet_reindex.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +"""Test wallet-reindex interaction""" + +import time + +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) +BLOCK_TIME = 60 * 10 + +class WalletReindexTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def advance_time(self, node, secs): + self.node_time += secs + node.setmocktime(self.node_time) + + # Verify the wallet updates the birth time accordingly when it detects a transaction + # with a time older than the oldest descriptor timestamp. + # This could happen when the user blindly imports a descriptor with 'timestamp=now'. + def birthtime_test(self, node, miner_wallet): + self.log.info("Test birth time update during tx scanning") + # Fund address to test + wallet_addr = miner_wallet.getnewaddress() + tx_id = miner_wallet.sendtoaddress(wallet_addr, 2) + + # Generate 50 blocks, one every 10 min to surpass the 2 hours rescan window the wallet has + for _ in range(50): + self.generate(node, 1) + self.advance_time(node, BLOCK_TIME) + + # Now create a new wallet, and import the descriptor + node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True) + wallet_watch_only = node.get_wallet_rpc('watch_only') + # Blank wallets don't have a birth time + assert 'birthtime' not in wallet_watch_only.getwalletinfo() + + # For a descriptors wallet: Import address with timestamp=now. + # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1. + # In both cases, disable rescan to not detect the transaction. + wallet_watch_only.importaddress(wallet_addr, rescan=False) + assert_equal(len(wallet_watch_only.listtransactions()), 0) + + # Depending on the wallet type, the birth time changes. + wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime'] + if self.options.descriptors: + # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min. + assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime) + else: + # No way of importing scripts/addresses with a custom time on a legacy wallet. + # It's always set to the beginning of time. + assert_equal(wallet_birthtime, 1) + + # Rescan the wallet to detect the missing transaction + wallet_watch_only.rescanblockchain() + assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50) + assert_equal(wallet_watch_only.getbalances()['mine' if self.options.descriptors else 'watchonly']['trusted'], 2) + + # Reindex and wait for it to finish + with node.assert_debug_log(expected_msgs=["initload thread exit"]): + self.restart_node(0, extra_args=['-reindex=1', f'-mocktime={self.node_time}']) + node.syncwithvalidationinterfacequeue() + + # Verify the transaction is still 'confirmed' after reindex + wallet_watch_only = node.get_wallet_rpc('watch_only') + tx_info = wallet_watch_only.gettransaction(tx_id) + assert_equal(tx_info['confirmations'], 50) + + # Depending on the wallet type, the birth time changes. + if self.options.descriptors: + # For descriptors, verify the wallet updated the birth time to the transaction time + assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime']) + else: + # For legacy, as the birth time was set to the beginning of time, verify it did not change + assert_equal(wallet_birthtime, 1) + + wallet_watch_only.unloadwallet() + + def run_test(self): + node = self.nodes[0] + self.node_time = int(time.time()) + node.setmocktime(self.node_time) + + # Fund miner + node.createwallet(wallet_name='miner', load_on_startup=True) + miner_wallet = node.get_wallet_rpc('miner') + self.generatetoaddress(node, COINBASE_MATURITY + 10, miner_wallet.getnewaddress()) + + # Tests + self.birthtime_test(node, miner_wallet) + + +if __name__ == '__main__': + WalletReindexTest().main() diff --git a/test/functional/wallet_signrawtransactionwithwallet.py b/test/functional/wallet_signrawtransactionwithwallet.py index d560dfdc11..b0517f951d 100755 --- a/test/functional/wallet_signrawtransactionwithwallet.py +++ b/test/functional/wallet_signrawtransactionwithwallet.py @@ -14,7 +14,6 @@ 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.messages import ( CTxInWitness, @@ -194,13 +193,12 @@ class SignRawTransactionWithWalletTest(BitcoinTestFramework): address = script_to_p2wsh(script) # Fund that address and make the spend - txid = self.nodes[0].sendtoaddress(address, 1) - vout = find_vout_for_address(self.nodes[0], txid, address) + utxo1 = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0] self.generate(self.nodes[0], 1) - utxo = self.nodes[0].listunspent()[0] - amt = Decimal(1) + utxo["amount"] - Decimal(0.00001) + utxo2 = self.nodes[0].listunspent()[0] + amt = Decimal(1) + utxo2["amount"] - Decimal(0.00001) tx = self.nodes[0].createrawtransaction( - [{"txid": txid, "vout": vout, "sequence": 1},{"txid": utxo["txid"], "vout": utxo["vout"]}], + [{**utxo1, "sequence": 1},{"txid": utxo2["txid"], "vout": utxo2["vout"]}], [{self.nodes[0].getnewaddress(): amt}], self.nodes[0].getblockcount() ) @@ -229,13 +227,12 @@ class SignRawTransactionWithWalletTest(BitcoinTestFramework): address = script_to_p2wsh(script) # Fund that address and make the spend - txid = self.nodes[0].sendtoaddress(address, 1) - vout = find_vout_for_address(self.nodes[0], txid, address) + utxo1 = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0] self.generate(self.nodes[0], 1) - utxo = self.nodes[0].listunspent()[0] - amt = Decimal(1) + utxo["amount"] - Decimal(0.00001) + utxo2 = self.nodes[0].listunspent()[0] + amt = Decimal(1) + utxo2["amount"] - Decimal(0.00001) tx = self.nodes[0].createrawtransaction( - [{"txid": txid, "vout": vout},{"txid": utxo["txid"], "vout": utxo["vout"]}], + [utxo1, {"txid": utxo2["txid"], "vout": utxo2["vout"]}], [{self.nodes[0].getnewaddress(): amt}], self.nodes[0].getblockcount() ) diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index d8ef66d83a..1f3b6f2ce9 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -7,7 +7,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - find_vout_for_address ) from test_framework.messages import ( COIN, @@ -35,8 +34,8 @@ class TxnMallTest(BitcoinTestFramework): super().setup_network() self.disconnect_nodes(1, 2) - def spend_txid(self, txid, vout, outputs): - inputs = [{"txid": txid, "vout": vout}] + def spend_utxo(self, utxo, outputs): + inputs = [utxo] tx = self.nodes[0].createrawtransaction(inputs, outputs) tx = self.nodes[0].fundrawtransaction(tx) tx = self.nodes[0].signrawtransactionwithwallet(tx['hex']) @@ -56,13 +55,13 @@ class TxnMallTest(BitcoinTestFramework): self.nodes[0].settxfee(.001) node0_address1 = self.nodes[0].getnewaddress(address_type=output_type) - node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219) - node0_tx1 = self.nodes[0].gettransaction(node0_txid1) - self.nodes[0].lockunspent(False, [{"txid":node0_txid1, "vout": find_vout_for_address(self.nodes[0], node0_txid1, node0_address1)}]) + node0_utxo1 = self.create_outpoints(self.nodes[0], outputs=[{node0_address1: 1219}])[0] + node0_tx1 = self.nodes[0].gettransaction(node0_utxo1['txid']) + self.nodes[0].lockunspent(False, [node0_utxo1]) node0_address2 = self.nodes[0].getnewaddress(address_type=output_type) - node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29) - node0_tx2 = self.nodes[0].gettransaction(node0_txid2) + node0_utxo2 = self.create_outpoints(self.nodes[0], outputs=[{node0_address2: 29}])[0] + node0_tx2 = self.nodes[0].gettransaction(node0_utxo2['txid']) assert_equal(self.nodes[0].getbalance(), starting_balance + node0_tx1["fee"] + node0_tx2["fee"]) @@ -71,8 +70,8 @@ class TxnMallTest(BitcoinTestFramework): node1_address = self.nodes[1].getnewaddress() # Send tx1, and another transaction tx2 that won't be cloned - txid1 = self.spend_txid(node0_txid1, find_vout_for_address(self.nodes[0], node0_txid1, node0_address1), {node1_address: 40}) - txid2 = self.spend_txid(node0_txid2, find_vout_for_address(self.nodes[0], node0_txid2, node0_address2), {node1_address: 20}) + txid1 = self.spend_utxo(node0_utxo1, {node1_address: 40}) + txid2 = self.spend_utxo(node0_utxo2, {node1_address: 20}) # Construct a clone of tx1, to be malleated rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py index 38ebfe0d7a..3cd0cd3207 100755 --- a/test/functional/wallet_txn_doublespend.py +++ b/test/functional/wallet_txn_doublespend.py @@ -8,8 +8,6 @@ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - find_output, - find_vout_for_address ) @@ -31,8 +29,8 @@ class TxnMallTest(BitcoinTestFramework): super().setup_network() self.disconnect_nodes(1, 2) - def spend_txid(self, txid, vout, outputs): - inputs = [{"txid": txid, "vout": vout}] + def spend_utxo(self, utxo, outputs): + inputs = [utxo] tx = self.nodes[0].createrawtransaction(inputs, outputs) tx = self.nodes[0].fundrawtransaction(tx) tx = self.nodes[0].signrawtransactionwithwallet(tx['hex']) @@ -54,13 +52,13 @@ class TxnMallTest(BitcoinTestFramework): # Assign coins to foo and bar addresses: node0_address_foo = self.nodes[0].getnewaddress() - fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219) - fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) - self.nodes[0].lockunspent(False, [{"txid":fund_foo_txid, "vout": find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo)}]) + fund_foo_utxo = self.create_outpoints(self.nodes[0], outputs=[{node0_address_foo: 1219}])[0] + fund_foo_tx = self.nodes[0].gettransaction(fund_foo_utxo['txid']) + self.nodes[0].lockunspent(False, [fund_foo_utxo]) node0_address_bar = self.nodes[0].getnewaddress() - fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29) - fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) + fund_bar_utxo = self.create_outpoints(node=self.nodes[0], outputs=[{node0_address_bar: 29}])[0] + fund_bar_tx = self.nodes[0].gettransaction(fund_bar_utxo['txid']) assert_equal(self.nodes[0].getbalance(), starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]) @@ -71,13 +69,7 @@ class TxnMallTest(BitcoinTestFramework): # First: use raw transaction API to send 1240 BTC to node1_address, # but don't broadcast: doublespend_fee = Decimal('-.02') - rawtx_input_0 = {} - rawtx_input_0["txid"] = fund_foo_txid - rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219) - rawtx_input_1 = {} - rawtx_input_1["txid"] = fund_bar_txid - rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29) - inputs = [rawtx_input_0, rawtx_input_1] + inputs = [fund_foo_utxo, fund_bar_utxo] change_address = self.nodes[0].getnewaddress() outputs = {} outputs[node1_address] = 1240 @@ -87,8 +79,8 @@ class TxnMallTest(BitcoinTestFramework): assert_equal(doublespend["complete"], True) # Create two spends using 1 50 BTC coin each - txid1 = self.spend_txid(fund_foo_txid, find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo), {node1_address: 40}) - txid2 = self.spend_txid(fund_bar_txid, find_vout_for_address(self.nodes[0], fund_bar_txid, node0_address_bar), {node1_address: 20}) + txid1 = self.spend_utxo(fund_foo_utxo, {node1_address: 40}) + txid2 = self.spend_utxo(fund_bar_utxo, {node1_address: 20}) # Have node0 mine a block: if (self.options.mine_block): |