diff options
Diffstat (limited to 'test')
61 files changed, 1557 insertions, 151 deletions
diff --git a/test/README.md b/test/README.md index 7d4a26fb4e..fdbb91832a 100644 --- a/test/README.md +++ b/test/README.md @@ -272,8 +272,8 @@ gdb /home/example/bitcoind <pid> Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`. See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt -Often while debugging rpc calls from functional tests, the test might reach timeout before -process can return a response. Use `--timeout-factor 0` to disable all rpc timeouts for that partcular +Often while debugging RPC calls in functional tests, the test might time out before the +process can return a response. Use `--timeout-factor 0` to disable all RPC timeouts for that particular functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`. ##### Profiling diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 657faebffc..3127350872 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -44,6 +44,10 @@ [ "cHNidP8BAKOro2MDAwMDA5ggCAAA////CQAtAAD+///1AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAD+///1Zm9ybmV3nWx1Y2vmelLmegAAAAAAAAAAAAAAAAAAAAMKAwMDAwMDAwMDAwMACvMBA3FkAAAAAAAAAAAABAAlAAAAAAAAACEWDQ0zDQ0NDQ0NDQ0NCwEAAH9/f39/fwMAAABNo6P///kAAA==", "Input Taproot BIP32 keypath has an invalid length" + ], + [ + "cHNidP8BAIkCAAAAAapfm08b0MipBvW9thL06f8rMbeazW7TIa0W9plHj4WoAAAAAAD9////AoCWmAAAAAAAIlEgC+blBlIP1iijRWxqjw1u9H02sqr7y8fno6/LdnvGqPl895x2AAAAACJRIM5wyjSexMbADl4K+AI1/68zyaDlE7guKvrEDUAjwqU1AAAAAAABASsAlDV3AAAAACJRIDfCpO/CIAqc0JKgBhsCfaPGdyroYtmH+4gQK/Mnn72UIRZGOixxmh9h2gqDIecYHcQHRa8w+Sokc//iDiqXz7uMGRkAHzYIzlYAAIABAACAAAAAgAAAAABhAAAAARcgRjoscZofYdoKgyHnGB3EB0WvMPkqJHP/4g4ql8+7jBkAAQUg1YCB33LpmkGemw3ncz7fcnjhL/bBG/PjH8vpgr2L3cUBBgAhB9WAgd9y6ZpBnpsN53M+33J44S/2wRvz4x/L6YK9i93FGQAfNgjOVgAAgAEAAIAAAACAAAAAAGIAAAAAAQUg9jMNus8cd+GAosBk9wn+pNP9wn7A+jy2Vq0cy+siJ8wBBgAhB/YzDbrPHHfhgKLAZPcJ/qTT/cJ+wPo8tlatHMvrIifMGQAfNgjOVgAAgAEAAIAAAACAAQAAAFEBAAAA", + "Output Taproot tree must not be empty" ] ], "valid" : [ diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index 05d274a9fe..5b43fe4f8e 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -10,15 +10,21 @@ from test_framework.blocktools import ( NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment, create_block, + script_to_p2wsh_script, ) from test_framework.messages import ( COIN, COutPoint, CTransaction, CTxIn, + CTxInWitness, CTxOut, tx_from_hex, ) +from test_framework.script import ( + CScript, + OP_TRUE, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -26,7 +32,8 @@ from test_framework.util import ( assert_raises_rpc_error, softfork_active, ) -from test_framework.script_util import DUMMY_P2WPKH_SCRIPT + +SCRIPT_W0_SH_OP_TRUE = script_to_p2wsh_script(CScript([OP_TRUE])) SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31) SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height) @@ -42,11 +49,9 @@ class BIP68Test(BitcoinTestFramework): self.extra_args = [ [ '-testactivationheight=csv@432', - "-acceptnonstdtxn=1", ], [ '-testactivationheight=csv@432', - "-acceptnonstdtxn=0", ], ] @@ -100,7 +105,7 @@ class BIP68Test(BitcoinTestFramework): # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] - tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)] + tx1.vout = [CTxOut(value, SCRIPT_W0_SH_OP_TRUE)] tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) @@ -112,7 +117,9 @@ class BIP68Test(BitcoinTestFramework): tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] - tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.wit.vtxinwit = [CTxInWitness()] + tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx2.vout = [CTxOut(int(value - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex()) @@ -207,7 +214,7 @@ class BIP68Test(BitcoinTestFramework): value += utxos[j]["amount"]*COIN # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50 - tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2WPKH_SCRIPT)) + tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), SCRIPT_W0_SH_OP_TRUE)) rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"] if (using_sequence_locks and not should_pass): @@ -236,7 +243,7 @@ class BIP68Test(BitcoinTestFramework): tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] - tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] tx2 = tx_from_hex(tx2_raw) tx2.rehash() @@ -254,7 +261,9 @@ class BIP68Test(BitcoinTestFramework): tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] - tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx.wit.vtxinwit = [CTxInWitness()] + tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx.rehash() if (orig_tx.hash in node.getrawmempool()): @@ -367,7 +376,7 @@ class BIP68Test(BitcoinTestFramework): tx2 = CTransaction() tx2.nVersion = 1 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] - tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] # sign tx2 tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] @@ -382,7 +391,9 @@ class BIP68Test(BitcoinTestFramework): tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)] - tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx3.wit.vtxinwit = [CTxInWitness()] + tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex()) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 462deeae32..850cb8334c 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1297,7 +1297,7 @@ class FullBlockTest(BitcoinTestFramework): blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): blocks2.append(self.next_block("alt" + str(i))) - self.send_blocks(blocks2, False, force_send=True) + self.send_blocks(blocks2, False, force_send=False) # extend alt chain to trigger re-org block = self.next_block("alt" + str(chain1_tip + 1)) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 6c51a5ac31..eb31bca29a 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -186,11 +186,12 @@ class ConfArgsTest(BitcoinTestFramework): with self.nodes[0].assert_debug_log(expected_msgs=[ "Loaded 0 addresses from peers.dat", "DNS seeding disabled", - "Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n", + "Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n", ]): self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1']) assert time.time() - start < 60 self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6") # No peers.dat exists and dns seeds are disabled. # We expect the node will not add fixed seeds when explicitly disabled. diff --git a/test/functional/feature_discover.py b/test/functional/feature_discover.py new file mode 100755 index 0000000000..7f4b81114e --- /dev/null +++ b/test/functional/feature_discover.py @@ -0,0 +1,75 @@ +#!/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 -discover command.""" + +import socket + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +def is_valid_ipv4_address(address): + try: + socket.inet_aton(address) + except socket.error: + return False + return True + + +def is_valid_ipv6_address(address): + try: + socket.inet_pton(socket.AF_INET6, address) + except socket.error: + return False + return True + + +class DiscoverTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.bind_to_localhost_only = False + self.num_nodes = 1 + + def validate_addresses(self, addresses_obj): + for address_obj in addresses_obj: + address = address_obj['address'] + self.log.info(f"Validating {address}") + valid = (is_valid_ipv4_address(address) + or is_valid_ipv6_address(address)) + assert_equal(valid, True) + + def test_local_addresses(self, test_case, *, expect_empty=False): + self.log.info(f"Restart node with {test_case}") + self.restart_node(0, test_case) + network_info = self.nodes[0].getnetworkinfo() + network_enabled = [n for n in network_info['networks'] + if n['reachable'] and n['name'] in ['ipv4', 'ipv6']] + local_addrs = list(network_info["localaddresses"]) + if expect_empty or not network_enabled: + assert_equal(local_addrs, []) + elif len(local_addrs) > 0: + self.validate_addresses(local_addrs) + + def run_test(self): + test_cases = [ + ["-listen", "-discover"], + ["-discover"], + ] + + test_cases_empty = [ + ["-discover=0"], + ["-listen", "-discover=0"], + [], + ] + + for test_case in test_cases: + self.test_local_addresses(test_case, expect_empty=False) + + for test_case in test_cases_empty: + self.test_local_addresses(test_case, expect_empty=True) + + +if __name__ == '__main__': + DiscoverTest().main() diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 13c7326519..56d093c396 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -55,7 +55,6 @@ class InitStressTest(BitcoinTestFramework): b'Loading P2P addresses', b'Loading banlist', b'Loading block index', - b'Switching active chainstate', b'Checking all blk files are present', b'Loaded best chain:', b'init message: Verifying blocks', diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index dd3cdc96ca..18b079cd71 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -317,35 +317,67 @@ class ProxyTest(BitcoinTestFramework): self.stop_node(1) - self.log.info("Test passing invalid -proxy raises expected init error") - self.nodes[1].extra_args = ["-proxy=abc:def"] - msg = "Error: Invalid -proxy address or hostname: 'abc:def'" + self.log.info("Test passing invalid -proxy hostname raises expected init error") + self.nodes[1].extra_args = ["-proxy=abc..abc:23456"] + msg = "Error: Invalid -proxy address or hostname: 'abc..abc:23456'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) - self.log.info("Test passing invalid -onion raises expected init error") - self.nodes[1].extra_args = ["-onion=xyz:abc"] - msg = "Error: Invalid -onion address or hostname: 'xyz:abc'" + self.log.info("Test passing invalid -proxy port raises expected init error") + self.nodes[1].extra_args = ["-proxy=192.0.0.1:def"] + msg = "Error: Invalid port specified in -proxy: '192.0.0.1:def'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) - self.log.info("Test passing invalid -i2psam raises expected init error") - self.nodes[1].extra_args = ["-i2psam=def:xyz"] - msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'" + self.log.info("Test passing invalid -onion hostname raises expected init error") + self.nodes[1].extra_args = ["-onion=xyz..xyz:23456"] + msg = "Error: Invalid -onion address or hostname: 'xyz..xyz:23456'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) - msg = ( - "Error: Outbound connections restricted to Tor (-onlynet=onion) but " - "the proxy for reaching the Tor network is not provided (no -proxy= " - "and no -onion= given) or it is explicitly forbidden (-onion=0)" - ) - self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error") - self.nodes[1].extra_args = ["-onlynet=onion"] + self.log.info("Test passing invalid -onion port raises expected init error") + self.nodes[1].extra_args = ["-onion=192.0.0.1:def"] + msg = "Error: Invalid port specified in -onion: '192.0.0.1:def'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -i2psam hostname raises expected init error") + self.nodes[1].extra_args = ["-i2psam=def..def:23456"] + msg = "Error: Invalid -i2psam address or hostname: 'def..def:23456'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -i2psam port raises expected init error") + self.nodes[1].extra_args = ["-i2psam=192.0.0.1:def"] + msg = "Error: Invalid port specified in -i2psam: '192.0.0.1:def'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -onlynet=i2p without -i2psam raises expected init error") + self.nodes[1].extra_args = ["-onlynet=i2p"] + msg = "Error: Outbound connections restricted to i2p (-onlynet=i2p) but -i2psam is not provided" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -onlynet=cjdns without -cjdnsreachable raises expected init error") + self.nodes[1].extra_args = ["-onlynet=cjdns"] + msg = "Error: Outbound connections restricted to CJDNS (-onlynet=cjdns) but -cjdnsreachable is not provided" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error") + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but " + "the proxy for reaching the Tor network is explicitly forbidden: -onion=0" + ) for arg in ["-onion=0", "-noonion"]: self.nodes[1].extra_args = ["-onlynet=onion", arg] self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing -onlynet=onion without -proxy, -onion or -listenonion raises expected init error") + self.nodes[1].extra_args = ["-onlynet=onion", "-listenonion=0"] + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given" + ) + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing -onlynet=onion without -proxy or -onion but with -listenonion=1 is ok") + self.start_node(1, extra_args=["-onlynet=onion", "-listenonion=1"]) + self.stop_node(1) + self.log.info("Test passing unknown network to -onlynet raises expected init error") self.nodes[1].extra_args = ["-onlynet=abc"] msg = "Error: Unknown network specified in -onlynet: 'abc'" diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 2237a4171e..7603248ae5 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -386,7 +386,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_too_many_replacements_with_default_mempool_params(self): """ - Test rule 5 of BIP125 (do not allow replacements that cause more than 100 + Test rule 5 (do not allow replacements that cause more than 100 evictions) without having to rely on non-default mempool parameters. In order to do this, create a number of "root" UTXOs, and then hang @@ -405,7 +405,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # limit; 10 works. num_tx_graphs = 10 - # (Number of transactions per graph, BIP125 rule 5 failure expected) + # (Number of transactions per graph, rule 5 failure expected) cases = [ # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT # transactions. diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 67cdc5ca32..cbb2e0338b 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -1007,13 +1007,13 @@ def spenders_taproot_active(): # input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and # will execute sigops signature checks. SIGOPS_RATIO_SCRIPTS = [ - # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIG. + # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIG. lambda n, pk: (CScript([OP_DROP, pk] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_CHECKSIG]), n + 1), # 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), - # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD. + # 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. lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1), diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index eec1c8fffb..24252610be 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -288,6 +288,10 @@ class RESTTest (BitcoinTestFramework): # See if we can get 5 headers in one response self.generate(self.nodes[1], 5) + expected_filter = { + 'basic block filter index': {'synced': True, 'best_block_height': 208}, + } + self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter) json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 5}) assert_equal(len(json_obj), 5) # now we should have 5 header objects json_obj = self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 5}) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 65b37a4975..02ec18140c 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -65,7 +65,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['size'], self.mempool_size) self.log.info('Should not accept garbage to testmempoolaccept') - assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) + assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26)) assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py index 47ae0c762b..21721177e6 100755 --- a/test/functional/mempool_expiry.py +++ b/test/functional/mempool_expiry.py @@ -13,6 +13,7 @@ definable expiry timeout via the '-mempoolexpiry=<n>' command line argument from datetime import timedelta from test_framework.blocktools import COINBASE_MATURITY +from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -20,7 +21,6 @@ from test_framework.util import ( ) from test_framework.wallet import MiniWallet -DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours CUSTOM_MEMPOOL_EXPIRY = 10 # hours diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index 3b75b2bc2d..581cf5896e 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -122,11 +122,11 @@ class PrioritiseTransactionTest(BitcoinTestFramework): # Test `prioritisetransaction` invalid `dummy` txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000' - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0) assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0) # Test `prioritisetransaction` invalid `fee_delta` - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo') self.test_diamond() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 5e50e1ebce..3cbb948e3c 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -615,6 +615,27 @@ class CompactBlocksTest(BitcoinTestFramework): bad_peer.send_message(msg) bad_peer.wait_for_disconnect() + def test_low_work_compactblocks(self, test_node): + # A compactblock with insufficient work won't get its header included + node = self.nodes[0] + hashPrevBlock = int(node.getblockhash(node.getblockcount() - 150), 16) + block = self.build_block_on_tip(node) + block.hashPrevBlock = hashPrevBlock + block.solve() + + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block) + with self.nodes[0].assert_debug_log(['[net] Ignoring low-work compact block from peer 0']): + test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) + + tips = node.getchaintips() + found = False + for x in tips: + if x["hash"] == block.hash: + found = True + break + assert not found + def test_compactblocks_not_at_tip(self, test_node): node = self.nodes[0] # Test that requesting old compactblocks doesn't work. @@ -833,6 +854,9 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Testing compactblock requests/announcements not at chain tip...") self.test_compactblocks_not_at_tip(self.segwit_node) + self.log.info("Testing handling of low-work compact blocks...") + self.test_low_work_compactblocks(self.segwit_node) + self.log.info("Testing handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.segwit_node) diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py index fde1e4bfa2..7e26994511 100755 --- a/test/functional/p2p_dos_header_tree.py +++ b/test/functional/p2p_dos_header_tree.py @@ -22,6 +22,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.setup_clean_chain = True self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint self.num_nodes = 2 + self.extra_args = [["-minimumchainwork=0x0"], ["-minimumchainwork=0x0"]] def add_options(self, parser): parser.add_argument( @@ -62,7 +63,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.log.info("Feed all fork headers (succeeds without checkpoint)") # On node 0 it succeeds because checkpoints are disabled - self.restart_node(0, extra_args=['-nocheckpoints']) + self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0"]) peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) assert { diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py new file mode 100755 index 0000000000..991e3348ed --- /dev/null +++ b/test/functional/p2p_headers_sync_with_minchainwork.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019-2021 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 we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" + +from test_framework.test_framework import BitcoinTestFramework + +from test_framework.p2p import ( + P2PInterface, +) + +from test_framework.messages import ( + msg_headers, +) + +from test_framework.blocktools import ( + NORMAL_GBT_REQUEST_PARAMS, + create_block, +) + +from test_framework.util import assert_equal + +NODE1_BLOCKS_REQUIRED = 15 +NODE2_BLOCKS_REQUIRED = 2047 + + +class RejectLowDifficultyHeadersTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 4 + # Node0 has no required chainwork; node1 requires 15 blocks on top of the genesis block; node2 requires 2047 + self.extra_args = [["-minimumchainwork=0x0", "-checkblockindex=0"], ["-minimumchainwork=0x1f", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0", "-whitelist=noban@127.0.0.1"]] + + def setup_network(self): + self.setup_nodes() + self.reconnect_all() + self.sync_all() + + def disconnect_all(self): + self.disconnect_nodes(0, 1) + self.disconnect_nodes(0, 2) + self.disconnect_nodes(0, 3) + + def reconnect_all(self): + self.connect_nodes(0, 1) + self.connect_nodes(0, 2) + self.connect_nodes(0, 3) + + def test_chains_sync_when_long_enough(self): + self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree") + with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]): + self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED-1, sync_fun=self.no_op) + + # Node3 should always allow headers due to noban permissions + self.log.info("Check that node3 will sync headers (due to noban permissions)") + + def check_node3_chaintips(num_tips, tip_hash, height): + node3_chaintips = self.nodes[3].getchaintips() + assert(len(node3_chaintips) == num_tips) + assert { + 'height': height, + 'hash': tip_hash, + 'branchlen': height, + 'status': 'headers-only', + } in node3_chaintips + + check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED-1) + + for node in self.nodes[1:3]: + chaintips = node.getchaintips() + assert(len(chaintips) == 1) + assert { + 'height': 0, + 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', + 'branchlen': 0, + 'status': 'active', + } in chaintips + + self.log.info("Generate more blocks to satisfy node1's minchainwork requirement, and verify node2 still has no new headers in headers tree") + with self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=15)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 15"]): + self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED - self.nodes[0].getblockcount(), sync_fun=self.no_op) + self.sync_blocks(self.nodes[0:2]) # node3 will sync headers (noban permissions) but not blocks (due to minchainwork) + + assert { + 'height': 0, + 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', + 'branchlen': 0, + 'status': 'active', + } in self.nodes[2].getchaintips() + + assert(len(self.nodes[2].getchaintips()) == 1) + + self.log.info("Check that node3 accepted these headers as well") + check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED) + + self.log.info("Generate long chain for node0/node1/node3") + self.generate(self.nodes[0], NODE2_BLOCKS_REQUIRED-self.nodes[0].getblockcount(), sync_fun=self.no_op) + + self.log.info("Verify that node2 and node3 will sync the chain when it gets long enough") + self.sync_blocks() + + def test_peerinfo_includes_headers_presync_height(self): + self.log.info("Test that getpeerinfo() includes headers presync height") + + # Disconnect network, so that we can find our own peer connection more + # easily + self.disconnect_all() + + p2p = self.nodes[0].add_p2p_connection(P2PInterface()) + node = self.nodes[0] + + # Ensure we have a long chain already + current_height = self.nodes[0].getblockcount() + if (current_height < 3000): + self.generate(node, 3000-current_height, sync_fun=self.no_op) + + # Send a group of 2000 headers, forking from genesis. + new_blocks = [] + hashPrevBlock = int(node.getblockhash(0), 16) + for i in range(2000): + block = create_block(hashprev = hashPrevBlock, tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) + block.solve() + new_blocks.append(block) + hashPrevBlock = block.sha256 + + headers_message = msg_headers(headers=new_blocks) + p2p.send_and_ping(headers_message) + + # getpeerinfo should show a sync in progress + assert_equal(node.getpeerinfo()[0]['presynced_headers'], 2000) + + def test_large_reorgs_can_succeed(self): + self.log.info("Test that a 2000+ block reorg, starting from a point that is more than 2000 blocks before a locator entry, can succeed") + + self.sync_all() # Ensure all nodes are synced. + self.disconnect_all() + + # locator(block at height T) will have heights: + # [T, T-1, ..., T-10, T-12, T-16, T-24, T-40, T-72, T-136, T-264, + # T-520, T-1032, T-2056, T-4104, ...] + # So mine a number of blocks > 4104 to ensure that the first window of + # received headers during a sync are fully between locator entries. + BLOCKS_TO_MINE = 4110 + + self.generate(self.nodes[0], BLOCKS_TO_MINE, sync_fun=self.no_op) + self.generate(self.nodes[1], BLOCKS_TO_MINE+2, sync_fun=self.no_op) + + self.reconnect_all() + + self.sync_blocks(timeout=300) # Ensure tips eventually agree + + + def run_test(self): + self.test_chains_sync_when_long_enough() + + self.test_large_reorgs_can_succeed() + + self.test_peerinfo_includes_headers_presync_height() + + + +if __name__ == '__main__': + RejectLowDifficultyHeadersTest().main() diff --git a/test/functional/p2p_i2p_sessions.py b/test/functional/p2p_i2p_sessions.py new file mode 100755 index 0000000000..4e52522b81 --- /dev/null +++ b/test/functional/p2p_i2p_sessions.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022-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 whether persistent or transient I2P sessions are being used, based on `-i2pacceptincoming`. +""" + +from test_framework.test_framework import BitcoinTestFramework + + +class I2PSessions(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + # The test assumes that an I2P SAM proxy is not listening here. + self.extra_args = [ + ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=1"], + ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=0"], + ] + + def run_test(self): + addr = "zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p" + + self.log.info("Ensure we create a persistent session when -i2pacceptincoming=1") + node0 = self.nodes[0] + with node0.assert_debug_log(expected_msgs=[f"Creating persistent SAM session"]): + node0.addnode(node=addr, command="onetry") + + self.log.info("Ensure we create a transient session when -i2pacceptincoming=0") + node1 = self.nodes[1] + with node1.assert_debug_log(expected_msgs=[f"Creating transient SAM session"]): + node1.addnode(node=addr, command="onetry") + + +if __name__ == '__main__': + I2PSessions().main() diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py new file mode 100755 index 0000000000..e67c384da7 --- /dev/null +++ b/test/functional/p2p_initial_headers_sync.py @@ -0,0 +1,105 @@ +#!/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 initial headers download + +Test that we only try to initially sync headers from one peer (until our chain +is close to caught up), and that each block announcement results in only one +additional peer receiving a getheaders message. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + CInv, + MSG_BLOCK, + msg_headers, + msg_inv, +) +from test_framework.p2p import ( + p2p_lock, + P2PInterface, +) +from test_framework.util import ( + assert_equal, +) +import random + +class HeadersSyncTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def announce_random_block(self, peers): + new_block_announcement = msg_inv(inv=[CInv(MSG_BLOCK, random.randrange(1<<256))]) + for p in peers: + p.send_and_ping(new_block_announcement) + + def run_test(self): + self.log.info("Adding a peer to node0") + peer1 = self.nodes[0].add_p2p_connection(P2PInterface()) + + # Wait for peer1 to receive a getheaders + peer1.wait_for_getheaders() + # An empty reply will clear the outstanding getheaders request, + # allowing additional getheaders requests to be sent to this peer in + # the future. + peer1.send_message(msg_headers()) + + self.log.info("Connecting two more peers to node0") + # Connect 2 more peers; they should not receive a getheaders yet + peer2 = self.nodes[0].add_p2p_connection(P2PInterface()) + peer3 = self.nodes[0].add_p2p_connection(P2PInterface()) + + all_peers = [peer1, peer2, peer3] + + self.log.info("Verify that peer2 and peer3 don't receive a getheaders after connecting") + for p in all_peers: + p.sync_with_ping() + with p2p_lock: + assert "getheaders" not in peer2.last_message + assert "getheaders" not in peer3.last_message + + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Have all peers announce a new block") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + peer1.send_message(msg_headers()) # Send empty response, see above + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response") + count = 0 + peer_receiving_getheaders = None + for p in [peer2, peer3]: + with p2p_lock: + if "getheaders" in p.last_message: + count += 1 + peer_receiving_getheaders = p + p.last_message.pop("getheaders", None) + p.send_message(msg_headers()) # Send empty response, see above + + assert_equal(count, 1) + + self.log.info("Announce another new block, from all peers") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + + self.log.info("Check that the remaining peer received a getheaders as well") + expected_peer = peer2 + if peer2 == peer_receiving_getheaders: + expected_peer = peer3 + + expected_peer.wait_for_getheaders() + + self.log.info("Success!") + +if __name__ == '__main__': + HeadersSyncTest().main() + diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index af8e45d578..936c22197c 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -138,6 +138,9 @@ class P2PLeakTest(BitcoinTestFramework): # Give the node enough time to possibly leak out a message time.sleep(PEER_TIMEOUT + 2) + self.log.info("Connect peer to ensure the net thread runs the disconnect logic at least once") + self.nodes[0].add_p2p_connection(P2PInterface()) + # Make sure only expected messages came in assert not no_version_idle_peer.unexpected_msg assert not no_version_idle_peer.got_wtxidrelay @@ -169,7 +172,7 @@ class P2PLeakTest(BitcoinTestFramework): self.log.info('Check that old peers are disconnected') p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) - with self.nodes[0].assert_debug_log(['peer=4 using obsolete version 31799; disconnecting']): + with self.nodes[0].assert_debug_log(["using obsolete version 31799; disconnecting"]): p2p_old_peer.send_message(self.create_old_version(31799)) p2p_old_peer.wait_for_disconnect() diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 76d9b045ce..5030e7af26 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -72,6 +72,13 @@ class AcceptBlockTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() + def check_hash_in_chaintips(self, node, blockhash): + tips = node.getchaintips() + for x in tips: + if x["hash"] == blockhash: + return True + return False + def run_test(self): test_node = self.nodes[0].add_p2p_connection(P2PInterface()) min_work_node = self.nodes[1].add_p2p_connection(P2PInterface()) @@ -89,10 +96,15 @@ class AcceptBlockTest(BitcoinTestFramework): blocks_h2[i].solve() block_time += 1 test_node.send_and_ping(msg_block(blocks_h2[0])) - min_work_node.send_and_ping(msg_block(blocks_h2[1])) + + with self.nodes[1].assert_debug_log(expected_msgs=[f"AcceptBlockHeader: not adding new block header {blocks_h2[1].hash}, missing anti-dos proof-of-work validation"]): + min_work_node.send_and_ping(msg_block(blocks_h2[1])) assert_equal(self.nodes[0].getblockcount(), 2) assert_equal(self.nodes[1].getblockcount(), 1) + + # Ensure that the header of the second block was also not accepted by node1 + assert_equal(self.check_hash_in_chaintips(self.nodes[1], blocks_h2[1].hash), False) self.log.info("First height 2 block accepted by node0; correctly rejected by node1") # 3. Send another block that builds on genesis. diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index d07b144905..d07d28879e 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -38,6 +38,7 @@ from test_framework.messages import ( msg_block, ) from test_framework.p2p import P2PInterface +from test_framework.script import hash256 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -261,12 +262,12 @@ class BlockchainTest(BitcoinTestFramework): assert_raises_rpc_error(-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0) # Test `getchaintxstats` invalid `nblocks` - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '') assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1) assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, self.nodes[0].getblockcount()) # Test `getchaintxstats` invalid `blockhash` - assert_raises_rpc_error(-1, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0) assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0') assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000') assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000') @@ -452,8 +453,9 @@ class BlockchainTest(BitcoinTestFramework): # (Previously this was broken based on setting # `rpc/blockchain.cpp:latestblock` incorrectly.) # - b20hash = node.getblockhash(20) - b20 = node.getblock(b20hash) + fork_height = current_height - 100 # choose something vaguely near our tip + fork_hash = node.getblockhash(fork_height) + fork_block = node.getblock(fork_hash) def solve_and_send_block(prevhash, height, time): b = create_block(prevhash, create_coinbase(height), time) @@ -461,10 +463,10 @@ class BlockchainTest(BitcoinTestFramework): peer.send_and_ping(msg_block(b)) return b - b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1) - b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1) + b1 = solve_and_send_block(int(fork_hash, 16), fork_height+1, fork_block['time'] + 1) + b2 = solve_and_send_block(b1.sha256, fork_height+2, b1.nTime + 1) - node.invalidateblock(b22f.hash) + node.invalidateblock(b2.hash) def assert_waitforheight(height, timeout=2): assert_equal( @@ -484,6 +486,10 @@ class BlockchainTest(BitcoinTestFramework): self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node) blockhash = self.generate(node, 1)[0] + def assert_hexblock_hashes(verbosity): + block = node.getblock(blockhash, verbosity) + assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex()) + def assert_fee_not_in_block(verbosity): block = node.getblock(blockhash, verbosity) assert 'fee' not in block['tx'][1] @@ -518,8 +524,13 @@ class BlockchainTest(BitcoinTestFramework): for vin in tx["vin"]: assert "prevout" not in vin + self.log.info("Test that getblock with verbosity 0 hashes to expected value") + assert_hexblock_hashes(0) + assert_hexblock_hashes(False) + self.log.info("Test that getblock with verbosity 1 doesn't include fee") assert_fee_not_in_block(1) + assert_fee_not_in_block(True) self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee') assert_fee_in_block(2) @@ -536,7 +547,7 @@ class BlockchainTest(BitcoinTestFramework): datadir = get_datadir_path(self.options.tmpdir, 0) self.log.info("Test getblock with invalid verbosity type returns proper error message") - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") def move_block_file(old, new): old_path = os.path.join(datadir, self.chain, 'blocks', old) diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py index 51b7efb4c3..b057400887 100755 --- a/test/functional/rpc_estimatefee.py +++ b/test/functional/rpc_estimatefee.py @@ -22,15 +22,15 @@ class EstimateFeeTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee) # wrong type for conf_target - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimatesmartfee, 'foo') - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo') # wrong type for estimatesmartfee(estimate_mode) - assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1) assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo') # wrong type for estimaterawfee(threshold) - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo') # extra params assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1) diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index cf9ad3f458..17c6fce9c2 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -106,6 +106,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.generate(self.nodes[2], 1) self.generate(self.nodes[0], 121) + self.test_add_inputs_default_value() self.test_weight_calculation() self.test_change_position() self.test_simple() @@ -301,7 +302,7 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None}) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None}) assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''}) rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'}) dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex']) @@ -408,7 +409,7 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) + assert_raises_rpc_error(-4, "Unable to find UTXO for external input", self.nodes[2].fundrawtransaction, rawtx) def test_fee_p2pkh(self): """Compare fee of a standard pubkeyhash transaction.""" @@ -635,7 +636,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn fee with many inputs") # Empty node1, send some small coins from node0 to node1. - self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) + self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()]) self.generate(self.nodes[1], 1) for _ in range(20): @@ -661,7 +662,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn sign+send with many inputs") # Again, empty node1, send some small coins from node0 to node1. - self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) + self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()]) self.generate(self.nodes[1], 1) for _ in range(20): @@ -1073,23 +1074,149 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].unloadwallet("extfund") + def test_add_inputs_default_value(self): + self.log.info("Test 'add_inputs' default value") + + # Create and fund the wallet with 5 BTC + self.nodes[2].createwallet("test_preset_inputs") + wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs") + addr1 = wallet.getnewaddress(address_type="bech32") + self.nodes[0].sendtoaddress(addr1, 5) + self.generate(self.nodes[0], 1) + + # Covered cases: + # 1. Default add_inputs value with no preset inputs (add_inputs=true): + # Expect: automatically add coins from the wallet to the tx. + # 2. Default add_inputs value with preset inputs (add_inputs=false): + # Expect: disallow automatic coin selection. + # 3. Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount). + # Expect: include inputs from the wallet. + # 4. Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount). + # Expect: only preset inputs are used. + # 5. Explicit add_inputs=true, no preset inputs (same as (1) but with an explicit set): + # Expect: include inputs from the wallet. + + # Case (1), 'send' command + # 'add_inputs' value is true unless "inputs" are specified, in such case, add_inputs=false. + # So, the wallet will automatically select coins and create the transaction if only the outputs are provided. + tx = wallet.send(outputs=[{addr1: 3}]) + assert tx["complete"] + + # Case (2), 'send' command + # Select an input manually, which doesn't cover the entire output amount and + # verify that the dynamically set 'add_inputs=false' value works. + + # Fund wallet with 2 outputs, 5 BTC each. + addr2 = wallet.getnewaddress(address_type="bech32") + source_tx = self.nodes[0].send(outputs=[{addr1: 5}, {addr2: 5}], options={"change_position": 0}) + self.generate(self.nodes[0], 1) + + # Select only one input. + options = { + "inputs": [ + { + "txid": source_tx["txid"], + "vout": 1 # change position was hardcoded to index 0 + } + ] + } + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 8}], options=options) + + # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount) + options["add_inputs"] = True + options["add_to_wallet"] = False + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + + # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount) + options["inputs"].append({ + "txid": source_tx["txid"], + "vout": 2 # change position was hardcoded to index 0 + }) + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + # Check that only the preset inputs were added to the tx + decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin'] + assert_equal(len(decoded_psbt_inputs), 2) + for input in decoded_psbt_inputs: + assert_equal(input["txid"], source_tx["txid"]) + + # Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true + options = {"add_inputs": True, "add_to_wallet": True} + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + + ################################################ + + # Case (1), 'walletcreatefundedpsbt' command + # Default add_inputs value with no preset inputs (add_inputs=true) + inputs = [] + outputs = {self.nodes[1].getnewaddress(): 8} + assert "psbt" in wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs) + + # Case (2), 'walletcreatefundedpsbt' command + # Default add_inputs value with preset inputs (add_inputs=false). + inputs = [{ + "txid": source_tx["txid"], + "vout": 1 # change position was hardcoded to index 0 + }] + outputs = {self.nodes[1].getnewaddress(): 8} + assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs) + + # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount) + options["add_inputs"] = True + options["add_to_wallet"] = False + assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options) + + # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount) + inputs.append({ + "txid": source_tx["txid"], + "vout": 2 # change position was hardcoded to index 0 + }) + psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options) + # Check that only the preset inputs were added to the tx + decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin'] + assert_equal(len(decoded_psbt_inputs), 2) + for input in decoded_psbt_inputs: + assert_equal(input["txid"], source_tx["txid"]) + + # Case (5), 'walletcreatefundedpsbt' command + # Explicit add_inputs=true, no preset inputs + options = { + "add_inputs": True + } + assert "psbt" in wallet.walletcreatefundedpsbt(inputs=[], outputs=outputs, options=options) + + self.nodes[2].unloadwallet("test_preset_inputs") + def test_weight_calculation(self): self.log.info("Test weight calculation with external inputs") self.nodes[2].createwallet("test_weight_calculation") wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation") - addr = wallet.getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 5) + 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) - self.nodes[0].sendtoaddress(wallet.getnewaddress(), 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(): 9.999}]) - fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10}) + rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{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_size = 10 + 41*2 + 31*2 + (2 + 107*2)/4 + # 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) + tx_size = ceil(10 + 41*2 + 31*2 + (2 + 107*2)/4) + 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}]) + 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) + tx_size = ceil(10 + 41*3 + 31*2 + (2 + 107*2 + 108)/4) assert_equal(fundedtx['fee'] * COIN, tx_size * 10) self.nodes[2].unloadwallet("test_weight_calculation") diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index 41e430d87e..278a343b2b 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -56,8 +56,8 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.log.info("Arguments must be valid") assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id) - assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id) - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].getblockfrompeer, short_tip, "0") + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0") self.log.info("We must already have the header") assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py index 5e6fd66aab..1b0f411e52 100755 --- a/test/functional/rpc_getdescriptorinfo.py +++ b/test/functional/rpc_getdescriptorinfo.py @@ -29,7 +29,7 @@ class DescriptorTest(BitcoinTestFramework): def run_test(self): assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo) - assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1) + assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1) assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "") # P2PK output with the specified public key. diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index 5b7e724728..f683577c47 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -92,7 +92,7 @@ class HelpRpcTest(BitcoinTestFramework): assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar') # invalid argument - assert_raises_rpc_error(-1, "JSON value of type number is not of expected type string", node.help, 0) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0) # help of unknown command assert_equal(node.help('foo'), 'help: unknown command: foo') diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py index f1c2537ef9..1e33e7ca9c 100755 --- a/test/functional/rpc_invalidateblock.py +++ b/test/functional/rpc_invalidateblock.py @@ -8,6 +8,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) @@ -83,6 +84,10 @@ class InvalidateTest(BitcoinTestFramework): # Should be back at the tip by now assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + self.log.info("Verify that invalidating an unknown block throws an error") + assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + if __name__ == '__main__': InvalidateTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 4583ca25cf..1fe3b21542 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -27,6 +27,7 @@ from test_framework.psbt import ( PSBT_IN_SHA256, PSBT_IN_HASH160, PSBT_IN_HASH256, + PSBT_OUT_TAP_TREE, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -779,9 +780,18 @@ class PSBTTest(BitcoinTestFramework): self.generate(self.nodes[0], 1) self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}]) - psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = watchonly.sendall([wallet.getnewaddress(), addr])["psbt"] psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] - self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + txid = self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + vout = find_vout_for_address(self.nodes[0], txid, addr) + + # Make sure tap tree is in psbt + parsed_psbt = PSBT.from_base64(psbt) + assert_greater_than(len(parsed_psbt.o[vout].map[PSBT_OUT_TAP_TREE]), 0) + assert "taproot_tree" in self.nodes[0].decodepsbt(psbt)["outputs"][vout] + parsed_psbt.make_blank() + comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()]) + assert_equal(comb_psbt, psbt) self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs") addr = self.nodes[0].getnewaddress("", "bech32m") @@ -793,6 +803,14 @@ class PSBTTest(BitcoinTestFramework): self.nodes[0].sendrawtransaction(rawtx) self.generate(self.nodes[0], 1) + # Make sure tap tree is not in psbt + parsed_psbt = PSBT.from_base64(psbt) + assert PSBT_OUT_TAP_TREE not in parsed_psbt.o[0].map + assert "taproot_tree" not in self.nodes[0].decodepsbt(psbt)["outputs"][0] + parsed_psbt.make_blank() + comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()]) + assert_equal(comb_psbt, psbt) + 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) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index a858292dd4..930aaaa897 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -124,13 +124,13 @@ class RawTransactionsTest(BitcoinTestFramework): # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose for value in ["True", "False"]: - assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value) # 7. invalid parameters - supply txid and empty array - assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txId, []) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, []) # 8. invalid parameters - supply txid and empty dict - assert_raises_rpc_error(-1, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {}) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {}) # Make a tx by sending, then generate 2 blocks; block1 has the tx in it tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid'] @@ -152,7 +152,7 @@ class RawTransactionsTest(BitcoinTestFramework): # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2) # An invalid block hash should raise the correct errors - assert_raises_rpc_error(-1, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True) + assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True) assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[n].getrawtransaction, txid=tx, blockhash="foobar") assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[n].getrawtransaction, txid=tx, blockhash="abcd1234") foo = "ZZZ0000000000000000000000000000000000000000000000000000000000000" @@ -180,9 +180,9 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo') # Test `createrawtransaction` invalid `inputs` - assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {}) - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {}) - assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {}) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {}) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {}) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {}) assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {}) txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844" assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {}) @@ -207,7 +207,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Test `createrawtransaction` invalid `outputs` address = getnewdestination()[2] - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo') self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility self.nodes[0].createrawtransaction(inputs=[], outputs=[]) assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'}) @@ -226,12 +226,12 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': MAX_BIP125_RBF_SEQUENCE+1}], {}, 0, True) # Test `createrawtransaction` invalid `locktime` - assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1) assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296) # Test `createrawtransaction` invalid `replaceable` - assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') # Test that createrawtransaction accepts an array and object as outputs # One output diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py new file mode 100755 index 0000000000..328095ee75 --- /dev/null +++ b/test/functional/rpc_scanblocks.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 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 the scanblocks RPC call.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + + +class ScanblocksTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [["-blockfilterindex=1"], []] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + # send 1.0, mempool only + addr_1 = node.getnewaddress() + node.sendtoaddress(addr_1, 1.0) + + parent_key = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B" + # send 1.0, mempool only + # childkey 5 of `parent_key` + node.sendtoaddress("mkS4HXoTYWRTescLGaUTGbtTTYX5EjJyEE", 1.0) + + # mine a block and assure that the mined blockhash is in the filterresult + blockhash = self.generate(node, 1)[0] + height = node.getblockheader(blockhash)['height'] + self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values())) + + out = node.scanblocks("start", [f"addr({addr_1})"]) + assert(blockhash in out['relevant_blocks']) + assert_equal(height, out['to_height']) + assert_equal(0, out['from_height']) + + # mine another block + blockhash_new = self.generate(node, 1)[0] + height_new = node.getblockheader(blockhash_new)['height'] + + # make sure the blockhash is not in the filter result if we set the start_height + # to the just mined block (unlikely to hit a false positive) + assert(blockhash not in node.scanblocks( + "start", [f"addr({addr_1})"], height_new)['relevant_blocks']) + + # make sure the blockhash is present when using the first mined block as start_height + assert(blockhash in node.scanblocks( + "start", [f"addr({addr_1})"], height)['relevant_blocks']) + + # also test the stop height + assert(blockhash in node.scanblocks( + "start", [f"addr({addr_1})"], height, height)['relevant_blocks']) + + # use the stop_height to exclude the relevant block + assert(blockhash not in node.scanblocks( + "start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks']) + + # make sure the blockhash is present when using the first mined block as start_height + assert(blockhash in node.scanblocks( + "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks']) + + # test node with disabled blockfilterindex + assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", + self.nodes[1].scanblocks, "start", [f"addr({addr_1})"]) + + # test unknown filtertype + assert_raises_rpc_error(-5, "Unknown filtertype", + node.scanblocks, "start", [f"addr({addr_1})"], 0, 10, "extended") + + # test invalid start_height + assert_raises_rpc_error(-1, "Invalid start_height", + node.scanblocks, "start", [f"addr({addr_1})"], 100000000) + + # test invalid stop_height + assert_raises_rpc_error(-1, "Invalid stop_height", + node.scanblocks, "start", [f"addr({addr_1})"], 10, 0) + assert_raises_rpc_error(-1, "Invalid stop_height", + node.scanblocks, "start", [f"addr({addr_1})"], 10, 100000000) + + # test accessing the status (must be empty) + assert_equal(node.scanblocks("status"), None) + + # test aborting the current scan (there is no, must return false) + assert_equal(node.scanblocks("abort"), False) + + # test invalid command + assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar") + + +if __name__ == '__main__': + ScanblocksTest().main() diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index acb6d3ea4a..6eb5b493b9 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -33,6 +33,9 @@ class ScantxoutsetTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.wallet.rescan_utxos() + self.log.info("Test if we find coinbase outputs.") + assert_equal(sum(u["coinbase"] for u in self.nodes[0].scantxoutset("start", [self.wallet.get_descriptor()])["unspents"]), 49) + self.log.info("Create UTXOs...") pubk1, spk_P2SH_SEGWIT, addr_P2SH_SEGWIT = getnewdestination("p2sh-segwit") pubk2, spk_LEGACY, addr_LEGACY = getnewdestination("legacy") diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 4e757b64ca..8a928a1e50 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -71,6 +71,7 @@ DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants # Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes. MAX_OP_RETURN_RELAY = 83 +DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours def sha256(s): return hashlib.sha256(s).digest() diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py index 68945e7e84..3a5b4ec74d 100644 --- a/test/functional/test_framework/psbt.py +++ b/test/functional/test_framework/psbt.py @@ -123,6 +123,15 @@ class PSBT: psbt = [x.serialize() for x in [self.g] + self.i + self.o] return b"psbt\xff" + b"".join(psbt) + def make_blank(self): + """ + Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX + """ + for m in self.i + self.o: + m.map.clear() + + self.g = PSBTMap(map={0: self.g.map[0]}) + def to_base64(self): return base64.b64encode(self.serialize()).decode("utf8") diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index c880aabd21..b1164b98fd 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -596,24 +596,24 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers) def disconnect_nodes(self, a, b): - def disconnect_nodes_helper(from_connection, node_num): - def get_peer_ids(): + def disconnect_nodes_helper(node_a, node_b): + def get_peer_ids(from_connection, node_num): result = [] for peer in from_connection.getpeerinfo(): if "testnode{}".format(node_num) in peer['subver']: result.append(peer['id']) return result - peer_ids = get_peer_ids() + peer_ids = get_peer_ids(node_a, node_b.index) if not peer_ids: self.log.warning("disconnect_nodes: {} and {} were not connected".format( - from_connection.index, - node_num, + node_a.index, + node_b.index, )) return for peer_id in peer_ids: try: - from_connection.disconnectnode(nodeid=peer_id) + node_a.disconnectnode(nodeid=peer_id) except JSONRPCException as e: # If this node is disconnected between calculating the peer id # and issuing the disconnect, don't worry about it. @@ -622,9 +622,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): raise # wait to disconnect - self.wait_until(lambda: not get_peer_ids(), timeout=5) + self.wait_until(lambda: not get_peer_ids(node_a, node_b.index), timeout=5) + self.wait_until(lambda: not get_peer_ids(node_b, node_a.index), timeout=5) - disconnect_nodes_helper(self.nodes[a], b) + disconnect_nodes_helper(self.nodes[a], self.nodes[b]) def split_network(self): """ diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 03f6c8adea..e35cae006f 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -118,6 +118,8 @@ class TestNode(): self.args.append("-logthreadnames") if self.version_is_at_least(219900): self.args.append("-logsourcelocations") + if self.version_is_at_least(239000): + self.args.append("-loglevel=trace") self.cli = TestNodeCLI(bitcoin_cli, self.datadir) self.use_cli = use_cli diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3676b698f0..628450f278 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -186,6 +186,7 @@ BASE_SCRIPTS = [ 'wallet_signrawtransactionwithwallet.py --legacy-wallet', 'wallet_signrawtransactionwithwallet.py --descriptors', 'rpc_signrawtransactionwithkey.py', + 'p2p_headers_sync_with_minchainwork.py', 'rpc_rawtransaction.py --legacy-wallet', 'wallet_groups.py --legacy-wallet', 'wallet_transactiontime_rescan.py --descriptors', @@ -230,8 +231,7 @@ BASE_SCRIPTS = [ 'rpc_getblockfrompeer.py', 'rpc_invalidateblock.py', 'feature_utxo_set_hash.py', - 'feature_rbf.py --legacy-wallet', - 'feature_rbf.py --descriptors', + 'feature_rbf.py', 'mempool_packages.py', 'mempool_package_onemore.py', 'rpc_createmultisig.py', @@ -247,6 +247,7 @@ BASE_SCRIPTS = [ 'rpc_generate.py', 'wallet_balance.py --legacy-wallet', 'wallet_balance.py --descriptors', + 'p2p_initial_headers_sync.py', 'feature_nulldummy.py', 'mempool_accept.py', 'mempool_expiry.py', @@ -276,6 +277,7 @@ BASE_SCRIPTS = [ 'feature_dersig.py', 'feature_cltv.py', 'rpc_uptime.py', + 'feature_discover.py', 'wallet_resendwallettransactions.py --legacy-wallet', 'wallet_resendwallettransactions.py --descriptors', 'wallet_fallbackfee.py --legacy-wallet', @@ -315,6 +317,7 @@ BASE_SCRIPTS = [ 'rpc_deriveaddresses.py', 'rpc_deriveaddresses.py --usecli', 'p2p_ping.py', + 'rpc_scanblocks.py', 'rpc_scantxoutset.py', 'feature_txindex_compatibility.py', 'feature_unsupported_utxo_db.py', @@ -329,6 +332,7 @@ BASE_SCRIPTS = [ 'feature_blocksdir.py', 'wallet_startup.py', 'p2p_i2p_ports.py', + 'p2p_i2p_sessions.py', 'feature_config_args.py', 'feature_presegwit_node_upgrade.py', 'feature_settings.py', @@ -338,6 +342,7 @@ BASE_SCRIPTS = [ 'feature_dirsymlinks.py', 'feature_help.py', 'feature_shutdown.py', + 'wallet_migration.py', 'p2p_ibd_txrelay.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time @@ -551,14 +556,14 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= while i < test_count: if failfast and not all_passed: break - for test_result, testdir, stdout, stderr in job_queue.get_next(): + for test_result, testdir, stdout, stderr, skip_reason in job_queue.get_next(): test_results.append(test_result) i += 1 done_str = "{}/{} - {}{}{}".format(i, test_count, BOLD[1], test_result.name, BOLD[0]) if test_result.status == "Passed": logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) elif test_result.status == "Skipped": - logging.debug("%s skipped" % (done_str)) + logging.debug(f"{done_str} skipped ({skip_reason})") else: all_passed = False print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) @@ -682,10 +687,12 @@ class TestHandler: log_out.seek(0), log_err.seek(0) [stdout, stderr] = [log_file.read().decode('utf-8') for log_file in (log_out, log_err)] log_out.close(), log_err.close() + skip_reason = None if proc.returncode == TEST_EXIT_PASSED and stderr == "": status = "Passed" elif proc.returncode == TEST_EXIT_SKIPPED: status = "Skipped" + skip_reason = re.search(r"Test Skipped: (.*)", stdout).group(1) else: status = "Failed" self.num_running -= 1 @@ -694,7 +701,7 @@ class TestHandler: clearline = '\r' + (' ' * dot_count) + '\r' print(clearline, end='', flush=True) dot_count = 0 - ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr)) + ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr, skip_reason)) if ret: return ret if self.use_term_control: diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 2cb9dc4523..1e5ce513cb 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -68,7 +68,7 @@ class ToolWalletTest(BitcoinTestFramework): result = 'unchanged' if new == old else 'increased!' self.log.debug('Wallet file timestamp {}'.format(result)) - def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0): + def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0, imported_privs=0): wallet_name = self.default_wallet_name if name == "" else name if self.options.descriptors: output_types = 4 # p2pkh, p2sh, segwit, bech32m @@ -83,7 +83,7 @@ class ToolWalletTest(BitcoinTestFramework): Keypool Size: %d Transactions: %d Address Book: %d - ''' % (wallet_name, keypool * output_types, transactions, address)) + ''' % (wallet_name, keypool * output_types, transactions, imported_privs * 3 + address)) else: output_types = 3 # p2pkh, p2sh, segwit. Legacy wallets do not support bech32m. return textwrap.dedent('''\ @@ -97,7 +97,7 @@ class ToolWalletTest(BitcoinTestFramework): Keypool Size: %d Transactions: %d Address Book: %d - ''' % (wallet_name, keypool, transactions, address * output_types)) + ''' % (wallet_name, keypool, transactions, (address + imported_privs) * output_types)) def read_dump(self, filename): dump = OrderedDict() @@ -219,7 +219,7 @@ class ToolWalletTest(BitcoinTestFramework): # shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = self.get_expected_info_output(address=1) + out = self.get_expected_info_output(imported_privs=1) self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') timestamp_after = self.wallet_timestamp() self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) @@ -250,7 +250,7 @@ class ToolWalletTest(BitcoinTestFramework): shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = self.get_expected_info_output(transactions=1, address=1) + out = self.get_expected_info_output(transactions=1, imported_privs=1) self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') shasum_after = self.wallet_shasum() timestamp_after = self.wallet_timestamp() diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 36fcdb36d6..d7850b41ac 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -24,6 +24,9 @@ class AbandonConflictTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [["-minrelaytxfee=0.00001"], []] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py index 46f41d9c22..cad9d02808 100755 --- a/test/functional/wallet_avoid_mixing_output_types.py +++ b/test/functional/wallet_avoid_mixing_output_types.py @@ -124,6 +124,7 @@ class AddressInputTypeGrouping(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + self.skip_if_no_sqlite() def make_payment(self, A, B, v, addr_type): fee_rate = random.randint(1, 20) diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index d49bca6855..ec58ace4a2 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -55,6 +55,9 @@ class WalletTest(BitcoinTestFramework): ['-limitdescendantcount=3', '-walletrejectlongchains=0'], [], ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -263,7 +266,6 @@ class WalletTest(BitcoinTestFramework): self.nodes[1].invalidateblock(block_reorg) assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted self.generatetoaddress(self.nodes[0], 1, ADDRESS_WATCHONLY, sync_fun=self.no_op) - assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted # Now confirm tx_orig self.restart_node(1, ['-persistmempool=0']) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 9cf1b3d2c4..20c577ceb3 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -7,6 +7,7 @@ from decimal import Decimal from itertools import product from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, @@ -25,7 +26,7 @@ class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.extra_args = [[ - "-dustrelayfee=0", "-walletrejectlongchains=0" + "-dustrelayfee=0", "-walletrejectlongchains=0", "-whitelist=noban@127.0.0.1" ]] * self.num_nodes self.setup_clean_chain = True self.supports_cli = False @@ -414,7 +415,7 @@ class WalletTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4") # This will raise an exception since generate does not accept a string - assert_raises_rpc_error(-1, "not of expected type number", self.generate, self.nodes[0], "2") + assert_raises_rpc_error(-3, "not of expected type number", self.generate, self.nodes[0], "2") if not self.options.descriptors: @@ -584,15 +585,9 @@ class WalletTest(BitcoinTestFramework): # ==Check that wallet prefers to use coins that don't exceed mempool limits ===== - # Get all non-zero utxos together + # Get all non-zero utxos together and split into two chains chain_addrs = [self.nodes[0].getnewaddress(), self.nodes[0].getnewaddress()] - singletxid = self.nodes[0].sendtoaddress(chain_addrs[0], self.nodes[0].getbalance(), "", "", True) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) - node0_balance = self.nodes[0].getbalance() - # Split into two chains - rawtx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], {chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')}) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - singletxid = self.nodes[0].sendrawtransaction(hexstring=signedtx["hex"], maxfeerate=0) + self.nodes[0].sendall(recipients=chain_addrs) self.generate(self.nodes[0], 1, sync_fun=self.no_op) # Make a long chain of unconfirmed payments without hitting mempool limit @@ -700,6 +695,39 @@ class WalletTest(BitcoinTestFramework): txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False) assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four) + if self.options.descriptors: + self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins") + # Create two multisig descriptors, and send a UTxO each. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01) + txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + # Now import the descriptors, make sure we can identify on which descriptor each coin was received. + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + coins = wo_wallet.listunspent(minconf=0) + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["txid"] == txid_a) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["txid"] == txid_b) + assert_equal(coin_b["parent_descs"][0], multi_b) + self.nodes[0].unloadwallet("wo") + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 9a481b290b..158ef66110 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -53,6 +53,7 @@ class BumpFeeTest(BitcoinTestFramework): "-walletrbf={}".format(i), "-mintxfee=0.00002", "-addresstype=bech32", + "-whitelist=noban@127.0.0.1", ] for i in range(self.num_nodes)] def skip_test_if_missing_module(self): @@ -86,12 +87,13 @@ class BumpFeeTest(BitcoinTestFramework): self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) test_nonrbf_bumpfee_fails(self, peer_node, dest_address) - test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address) + test_notmine_bumpfee(self, rbf_node, peer_node, dest_address) test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address) test_dust_to_fee(self, rbf_node, dest_address) test_watchonly_psbt(self, peer_node, rbf_node, dest_address) test_rebumping(self, rbf_node, dest_address) test_rebumping_not_replaceable(self, rbf_node, dest_address) + test_bumpfee_already_spent(self, rbf_node, dest_address) test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address) test_bumpfee_metadata(self, rbf_node, dest_address) test_locked_wallet_fails(self, rbf_node, dest_address) @@ -228,11 +230,11 @@ def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address): def test_nonrbf_bumpfee_fails(self, peer_node, dest_address): self.log.info('Test that we cannot replace a non RBF transaction') not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000")) - assert_raises_rpc_error(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) + assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) self.clear_mempool() -def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): +def test_notmine_bumpfee(self, rbf_node, peer_node, dest_address): self.log.info('Test that it cannot bump fee if non-owned inputs are included') # here, the rbftx has a peer_node coin and then adds a rbf_node input # Note that this test depends upon the RPC code checking input ownership prior to change outputs @@ -250,8 +252,27 @@ def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): signedtx = rbf_node.signrawtransactionwithwallet(rawtx) signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"]) rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) + entry = rbf_node.getmempoolentry(rbfid) + old_fee = entry["fees"]["base"] + old_feerate = int(old_fee / entry["vsize"] * Decimal(1e8)) assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet", rbf_node.bumpfee, rbfid) + + def finish_psbtbumpfee(psbt): + psbt = rbf_node.walletprocesspsbt(psbt) + psbt = peer_node.walletprocesspsbt(psbt["psbt"]) + final = rbf_node.finalizepsbt(psbt["psbt"]) + res = rbf_node.testmempoolaccept([final["hex"]]) + assert res[0]["allowed"] + assert_greater_than(res[0]["fees"]["base"], old_fee) + + self.log.info("Test that psbtbumpfee works for non-owned inputs") + psbt = rbf_node.psbtbumpfee(txid=rbfid) + finish_psbtbumpfee(psbt["psbt"]) + + psbt = rbf_node.psbtbumpfee(txid=rbfid, options={"fee_rate": old_feerate + 10}) + finish_psbtbumpfee(psbt["psbt"]) + self.clear_mempool() @@ -479,7 +500,8 @@ def test_rebumping(self, rbf_node, dest_address): self.log.info('Test that re-bumping the original tx fails, but bumping successor works') rbfid = spend_one_input(rbf_node, dest_address) bumped = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL}) - assert_raises_rpc_error(-4, "already bumped", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL}) + assert_raises_rpc_error(-4, f"Cannot bump transaction {rbfid} which was already bumped by transaction {bumped['txid']}", + rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL}) rbf_node.bumpfee(bumped["txid"], {"fee_rate": NORMAL}) self.clear_mempool() @@ -493,6 +515,15 @@ def test_rebumping_not_replaceable(self, rbf_node, dest_address): self.clear_mempool() +def test_bumpfee_already_spent(self, rbf_node, dest_address): + self.log.info('Test that bumping tx with already spent coin fails') + txid = spend_one_input(rbf_node, dest_address) + self.generate(rbf_node, 1) # spend coin simply by mining block with tx + spent_input = rbf_node.gettransaction(txid=txid, verbose=True)['decoded']['vin'][0] + assert_raises_rpc_error(-1, f"{spent_input['txid']}:{spent_input['vout']} is already spent", + rbf_node.bumpfee, txid, {"fee_rate": NORMAL}) + + def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address): self.log.info('Test that unconfirmed outputs from bumped txns are not spendable') rbfid = spend_one_input(rbf_node, rbf_node_address) @@ -604,7 +635,7 @@ def test_no_more_inputs_fails(self, rbf_node, dest_address): # feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient self.generatetoaddress(rbf_node, 1, dest_address) # spend all funds, no change output - rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True) + rbfid = rbf_node.sendall(recipients=[rbf_node.getnewaddress()])['txid'] assert_raises_rpc_error(-4, "Unable to create transaction. Insufficient funds", rbf_node.bumpfee, rbfid) self.clear_mempool() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 0c9106f800..37c1c4bff3 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -9,8 +9,7 @@ import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, - assert_greater_than, - assert_greater_than_or_equal, + assert_equal, ) @@ -76,21 +75,18 @@ class WalletEncryptionTest(BitcoinTestFramework): self.log.info('Check a timeout less than the limit') MAX_VALUE = 100000000 - expected_time = int(time.time()) + MAX_VALUE - 600 + now = int(time.time()) + self.nodes[0].setmocktime(now) + expected_time = now + MAX_VALUE - 600 self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600) - # give buffer for walletpassphrase, since it iterates over all encrypted keys - expected_time_with_buffer = time.time() + MAX_VALUE - 600 actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) self.log.info('Check a timeout greater than the limit') - expected_time = int(time.time()) + MAX_VALUE - 1 + expected_time = now + MAX_VALUE self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) - expected_time_with_buffer = time.time() + MAX_VALUE actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) if __name__ == '__main__': diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index eb305c5fa2..e5e4cf03bf 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -26,6 +26,11 @@ class WalletGroupTest(BitcoinTestFramework): ["-maxapsfee=0.00002719"], ["-maxapsfee=0.00002720"], ] + + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") # whitelist peers to speed up tx relay / mempool sync + args.append(f"-paytxfee={20 * 1e3 / 1e8}") # apply feerate of 20 sats/vB across all nodes + self.rpc_timeout = 480 def skip_test_if_missing_module(self): @@ -150,7 +155,7 @@ class WalletGroupTest(BitcoinTestFramework): assert_equal(2, len(tx6["vout"])) # Empty out node2's wallet - self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=self.nodes[2].getbalance(), subtractfeefromamount=True) + self.nodes[2].sendall(recipients=[self.nodes[0].getnewaddress()]) self.sync_all() self.generate(self.nodes[0], 1) diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 686a365584..220c856498 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -20,6 +20,10 @@ class WalletHDTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [[], ['-keypool=0']] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") + self.supports_cli = False def skip_test_if_missing_module(self): @@ -173,8 +177,8 @@ class WalletHDTest(BitcoinTestFramework): # Sethdseed parameter validity assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0) assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif") - assert_raises_rpc_error(-1, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool") - assert_raises_rpc_error(-1, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool") + assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index 525b91a6e0..9744009af8 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -35,6 +35,9 @@ class ImportDescriptorsTest(BitcoinTestFramework): self.extra_args = [["-addresstype=legacy"], ["-addresstype=bech32", "-keypool=5"] ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") self.setup_clean_chain = True self.wallet_names = [] diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 3953851491..62a1a3341d 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -874,6 +874,25 @@ class ImportMultiTest(BitcoinTestFramework): addr = wrpc.getnewaddress('', 'bech32') assert_equal(addr, addresses[i]) + # Create wallet with passphrase + self.log.info('Test watchonly imports on a wallet with a passphrase, without unlocking') + self.nodes[1].createwallet(wallet_name='w1', blank=True, passphrase='pass') + wrpc = self.nodes[1].get_wallet_rpc('w1') + assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", + wrpc.importmulti, [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + "timestamp": "now", + }]) + + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + "timestamp": "now", + "watchonly": True, + }] + ) + assert result[0]['success'] + if __name__ == '__main__': ImportMultiTest().main() diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py index 202ef92887..d5372f5aee 100755 --- a/test/functional/wallet_listdescriptors.py +++ b/test/functional/wallet_listdescriptors.py @@ -52,6 +52,10 @@ class ListDescriptorsTest(BitcoinTestFramework): assert item['range'] == [0, 0] assert item['timestamp'] is not None + self.log.info('Test that descriptor strings are returned in lexicographically sorted order.') + descriptor_strings = [descriptor['desc'] for descriptor in result['descriptors']] + assert_equal(descriptor_strings, sorted(descriptor_strings)) + self.log.info('Test descriptors with hardened derivations are listed in importable form.') xprv = 'tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg' xpub_acc = 'tpubDCMVLhErorrAGfApiJSJzEKwqeaf2z3NrkVMxgYQjZLzMjXMBeRw2muGNYbvaekAE8rUFLftyEar4LdrG2wXyyTJQZ26zptmeTEjPTaATts' diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 7ae3a40eaf..f1d7de2f27 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -18,6 +18,8 @@ from test_framework.wallet_util import test_address class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 1f3a276d9c..f259449bef 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -6,6 +6,7 @@ from test_framework.address import key_to_p2wpkh from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import MAX_BIP125_RBF_SEQUENCE @@ -22,6 +23,8 @@ class ListSinceBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -39,6 +42,9 @@ class ListSinceBlockTest(BitcoinTestFramework): self.test_double_send() self.double_spends_filtered() self.test_targetconfirmations() + if self.options.descriptors: + self.test_desc() + self.test_send_to_self() def test_no_blockhash(self): self.log.info("Test no blockhash") @@ -383,5 +389,65 @@ class ListSinceBlockTest(BitcoinTestFramework): assert_equal(original_found, False) assert_equal(double_found, False) + def test_desc(self): + """Make sure we can track coins by descriptor.""" + self.log.info("Test descriptor lookup by scriptPubKey.") + + # Create a watchonly wallet tracking two multisig descriptors. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + + # Send a coin to each descriptor. + assert_equal(len(wo_wallet.listsinceblock()["transactions"]), 0) + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + self.nodes[2].sendtoaddress(addr_a, 1) + self.nodes[2].sendtoaddress(addr_b, 2) + self.generate(self.nodes[2], 1) + + # We can identify on which descriptor each coin was received. + coins = wo_wallet.listsinceblock()["transactions"] + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["amount"] == 1) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["amount"] == 2) + assert_equal(coin_b["parent_descs"][0], multi_b) + + def test_send_to_self(self): + """We can make listsinceblock output our change outputs.""" + self.log.info("Test the inclusion of change outputs in the output.") + + # Create a UTxO paying to one of our change addresses. + block_hash = self.nodes[2].getbestblockhash() + addr = self.nodes[2].getrawchangeaddress() + self.nodes[2].sendtoaddress(addr, 1) + + # If we don't list change, we won't have an entry for it. + coins = self.nodes[2].listsinceblock(blockhash=block_hash)["transactions"] + assert not any(c["address"] == addr for c in coins) + + # Now if we list change, we'll get both the send (to a change address) and + # the actual change. + res = self.nodes[2].listsinceblock(blockhash=block_hash, include_change=True) + coins = [entry for entry in res["transactions"] if entry["category"] == "receive"] + assert_equal(len(coins), 2) + assert any(c["address"] == addr for c in coins) + assert all(self.nodes[2].getaddressinfo(c["address"])["ischange"] for c in coins) + + if __name__ == '__main__': ListSinceBlockTest().main() diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py new file mode 100755 index 0000000000..3c1cb6ac32 --- /dev/null +++ b/test/functional/wallet_migration.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 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 Migrating a wallet from legacy to descriptor.""" + +import os +import random +from test_framework.descriptors import descsum_create +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.wallet_util import ( + get_generate_key, +) + + +class WalletMigrationTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [[]] + self.supports_cli = False + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + self.skip_if_no_bdb() + + def assert_is_sqlite(self, wallet_name): + wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest/wallets", wallet_name, self.wallet_data_filename) + with open(wallet_file_path, 'rb') as f: + file_magic = f.read(16) + assert_equal(file_magic, b'SQLite format 3\x00') + assert_equal(self.nodes[0].get_wallet_rpc(wallet_name).getwalletinfo()["format"], "sqlite") + + def create_legacy_wallet(self, wallet_name): + self.nodes[0].createwallet(wallet_name=wallet_name) + wallet = self.nodes[0].get_wallet_rpc(wallet_name) + assert_equal(wallet.getwalletinfo()["descriptors"], False) + assert_equal(wallet.getwalletinfo()["format"], "bdb") + return wallet + + def assert_addr_info_equal(self, addr_info, addr_info_old): + assert_equal(addr_info["address"], addr_info_old["address"]) + assert_equal(addr_info["scriptPubKey"], addr_info_old["scriptPubKey"]) + assert_equal(addr_info["ismine"], addr_info_old["ismine"]) + assert_equal(addr_info["hdkeypath"], addr_info_old["hdkeypath"]) + assert_equal(addr_info["solvable"], addr_info_old["solvable"]) + assert_equal(addr_info["ischange"], addr_info_old["ischange"]) + assert_equal(addr_info["hdmasterfingerprint"], addr_info_old["hdmasterfingerprint"]) + + def assert_list_txs_equal(self, received_list_txs, expected_list_txs): + for d in received_list_txs: + if "parent_descs" in d: + del d["parent_descs"] + for d in expected_list_txs: + if "parent_descs" in d: + del d["parent_descs"] + assert_equal(received_list_txs, expected_list_txs) + + def test_basic(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + self.log.info("Test migration of a basic keys only wallet without balance") + basic0 = self.create_legacy_wallet("basic0") + + addr = basic0.getnewaddress() + change = basic0.getrawchangeaddress() + + old_addr_info = basic0.getaddressinfo(addr) + old_change_addr_info = basic0.getaddressinfo(change) + assert_equal(old_addr_info["ismine"], True) + assert_equal(old_addr_info["hdkeypath"], "m/0'/0'/0'") + assert_equal(old_change_addr_info["ismine"], True) + assert_equal(old_change_addr_info["hdkeypath"], "m/0'/1'/0'") + + # Note: migration could take a while. + basic0.migratewallet() + + # Verify created descriptors + assert_equal(basic0.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic0") + + # The wallet should create the following descriptors: + # * BIP32 descriptors in the form of "0'/0'/*" and "0'/1'/*" (2 descriptors) + # * BIP44 descriptors in the form of "44'/1'/0'/0/*" and "44'/1'/0'/1/*" (2 descriptors) + # * BIP49 descriptors, P2SH(P2WPKH), in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors) + # * BIP84 descriptors, P2WPKH, in the form of "84'/1'/0'/1/*" and "84'/1'/0'/1/*" (2 descriptors) + # * BIP86 descriptors, P2TR, in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors) + # * A combo(PK) descriptor for the wallet master key. + # So, should have a total of 11 descriptors on it. + assert_equal(len(basic0.listdescriptors()["descriptors"]), 11) + + # Compare addresses info + addr_info = basic0.getaddressinfo(addr) + change_addr_info = basic0.getaddressinfo(change) + self.assert_addr_info_equal(addr_info, old_addr_info) + self.assert_addr_info_equal(change_addr_info, old_change_addr_info) + + addr_info = basic0.getaddressinfo(basic0.getnewaddress("", "bech32")) + assert_equal(addr_info["hdkeypath"], "m/84'/1'/0'/0/0") + + self.log.info("Test migration of a basic keys only wallet with a balance") + basic1 = self.create_legacy_wallet("basic1") + + for _ in range(0, 10): + default.sendtoaddress(basic1.getnewaddress(), 1) + + self.generate(self.nodes[0], 1) + + for _ in range(0, 5): + basic1.sendtoaddress(default.getnewaddress(), 0.5) + + self.generate(self.nodes[0], 1) + bal = basic1.getbalance() + txs = basic1.listtransactions() + + basic1.migratewallet() + assert_equal(basic1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic1") + assert_equal(basic1.getbalance(), bal) + self.assert_list_txs_equal(basic1.listtransactions(), txs) + + # restart node and verify that everything is still there + self.restart_node(0) + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.nodes[0].loadwallet("basic1") + basic1 = self.nodes[0].get_wallet_rpc("basic1") + assert_equal(basic1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic1") + assert_equal(basic1.getbalance(), bal) + self.assert_list_txs_equal(basic1.listtransactions(), txs) + + self.log.info("Test migration of a wallet with balance received on the seed") + basic2 = self.create_legacy_wallet("basic2") + basic2_seed = get_generate_key() + basic2.sethdseed(True, basic2_seed.privkey) + assert_equal(basic2.getbalance(), 0) + + # Receive coins on different output types for the same seed + basic2_balance = 0 + for addr in [basic2_seed.p2pkh_addr, basic2_seed.p2wpkh_addr, basic2_seed.p2sh_p2wpkh_addr]: + send_value = random.randint(1, 4) + default.sendtoaddress(addr, send_value) + basic2_balance += send_value + self.generate(self.nodes[0], 1) + assert_equal(basic2.getbalance(), basic2_balance) + basic2_txs = basic2.listtransactions() + + # Now migrate and test that we still see have the same balance/transactions + basic2.migratewallet() + assert_equal(basic2.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic2") + assert_equal(basic2.getbalance(), basic2_balance) + self.assert_list_txs_equal(basic2.listtransactions(), basic2_txs) + + def test_multisig(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Contrived case where all the multisig keys are in a single wallet + self.log.info("Test migration of a wallet with all keys for a multisig") + multisig0 = self.create_legacy_wallet("multisig0") + addr1 = multisig0.getnewaddress() + addr2 = multisig0.getnewaddress() + addr3 = multisig0.getnewaddress() + + ms_info = multisig0.addmultisigaddress(2, [addr1, addr2, addr3]) + + multisig0.migratewallet() + assert_equal(multisig0.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("multisig0") + ms_addr_info = multisig0.getaddressinfo(ms_info["address"]) + assert_equal(ms_addr_info["ismine"], True) + assert_equal(ms_addr_info["desc"], ms_info["descriptor"]) + assert_equal("multisig0_watchonly" in self.nodes[0].listwallets(), False) + assert_equal("multisig0_solvables" in self.nodes[0].listwallets(), False) + + pub1 = multisig0.getaddressinfo(addr1)["pubkey"] + pub2 = multisig0.getaddressinfo(addr2)["pubkey"] + + # Some keys in multisig do not belong to this wallet + self.log.info("Test migration of a wallet that has some keys in a multisig") + self.nodes[0].createwallet(wallet_name="multisig1") + multisig1 = self.nodes[0].get_wallet_rpc("multisig1") + ms_info = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2]) + ms_info2 = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2]) + assert_equal(multisig1.getwalletinfo()["descriptors"], False) + + addr1 = ms_info["address"] + addr2 = ms_info2["address"] + txid = default.sendtoaddress(addr1, 10) + multisig1.importaddress(addr1) + assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], True) + assert_equal(multisig1.getaddressinfo(addr1)["solvable"], True) + self.generate(self.nodes[0], 1) + multisig1.gettransaction(txid) + assert_equal(multisig1.getbalances()["watchonly"]["trusted"], 10) + assert_equal(multisig1.getaddressinfo(addr2)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr2)["iswatchonly"], False) + assert_equal(multisig1.getaddressinfo(addr2)["solvable"], True) + + # Migrating multisig1 should see the multisig is no longer part of multisig1 + # A new wallet multisig1_watchonly is created which has the multisig address + # Transaction to multisig is in multisig1_watchonly and not multisig1 + multisig1.migratewallet() + assert_equal(multisig1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("multisig1") + assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], False) + assert_equal(multisig1.getaddressinfo(addr1)["solvable"], False) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", multisig1.gettransaction, txid) + assert_equal(multisig1.getbalance(), 0) + assert_equal(multisig1.listtransactions(), []) + + assert_equal("multisig1_watchonly" in self.nodes[0].listwallets(), True) + ms1_watchonly = self.nodes[0].get_wallet_rpc("multisig1_watchonly") + ms1_wallet_info = ms1_watchonly.getwalletinfo() + assert_equal(ms1_wallet_info['descriptors'], True) + assert_equal(ms1_wallet_info['private_keys_enabled'], False) + self.assert_is_sqlite("multisig1_watchonly") + assert_equal(ms1_watchonly.getaddressinfo(addr1)["ismine"], True) + assert_equal(ms1_watchonly.getaddressinfo(addr1)["solvable"], True) + # Because addr2 was not being watched, it isn't in multisig1_watchonly but rather multisig1_solvables + assert_equal(ms1_watchonly.getaddressinfo(addr2)["ismine"], False) + assert_equal(ms1_watchonly.getaddressinfo(addr2)["solvable"], False) + ms1_watchonly.gettransaction(txid) + assert_equal(ms1_watchonly.getbalance(), 10) + + # Migrating multisig1 should see the second multisig is no longer part of multisig1 + # A new wallet multisig1_solvables is created which has the second address + # This should have no transactions + assert_equal("multisig1_solvables" in self.nodes[0].listwallets(), True) + ms1_solvable = self.nodes[0].get_wallet_rpc("multisig1_solvables") + ms1_wallet_info = ms1_solvable.getwalletinfo() + assert_equal(ms1_wallet_info['descriptors'], True) + assert_equal(ms1_wallet_info['private_keys_enabled'], False) + self.assert_is_sqlite("multisig1_solvables") + assert_equal(ms1_solvable.getaddressinfo(addr1)["ismine"], False) + assert_equal(ms1_solvable.getaddressinfo(addr1)["solvable"], False) + assert_equal(ms1_solvable.getaddressinfo(addr2)["ismine"], True) + assert_equal(ms1_solvable.getaddressinfo(addr2)["solvable"], True) + assert_equal(ms1_solvable.getbalance(), 0) + assert_equal(ms1_solvable.listtransactions(), []) + + + def test_other_watchonly(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Wallet with an imported address. Should be the same thing as the multisig test + self.log.info("Test migration of a wallet with watchonly imports") + self.nodes[0].createwallet(wallet_name="imports0") + imports0 = self.nodes[0].get_wallet_rpc("imports0") + assert_equal(imports0.getwalletinfo()["descriptors"], False) + + # Exteranl address label + imports0.setlabel(default.getnewaddress(), "external") + + # Normal non-watchonly tx + received_addr = imports0.getnewaddress() + imports0.setlabel(received_addr, "Receiving") + received_txid = default.sendtoaddress(received_addr, 10) + + # Watchonly tx + import_addr = default.getnewaddress() + imports0.importaddress(import_addr) + imports0.setlabel(import_addr, "imported") + received_watchonly_txid = default.sendtoaddress(import_addr, 10) + + # 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()], options={"inputs": [{"txid": received_sent_watchonly_txid, "vout": received_sent_watchonly_vout}]}) + sent_watchonly_txid = send["txid"] + + self.generate(self.nodes[0], 1) + + balances = imports0.getbalances() + spendable_bal = balances["mine"]["trusted"] + watchonly_bal = balances["watchonly"]["trusted"] + assert_equal(len(imports0.listtransactions(include_watchonly=True)), 4) + + # Migrate + imports0.migratewallet() + 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, sent_watchonly_txid) + assert_equal(len(imports0.listtransactions(include_watchonly=True)), 1) + imports0.gettransaction(received_txid) + assert_equal(imports0.getbalance(), spendable_bal) + + assert_equal("imports0_watchonly" in self.nodes[0].listwallets(), True) + watchonly = self.nodes[0].get_wallet_rpc("imports0_watchonly") + watchonly_info = watchonly.getwalletinfo() + assert_equal(watchonly_info["descriptors"], True) + self.assert_is_sqlite("imports0_watchonly") + assert_equal(watchonly_info["private_keys_enabled"], False) + watchonly.gettransaction(received_watchonly_txid) + watchonly.gettransaction(received_sent_watchonly_txid) + watchonly.gettransaction(sent_watchonly_txid) + assert_equal(watchonly.getbalance(), watchonly_bal) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid) + assert_equal(len(watchonly.listtransactions(include_watchonly=True)), 3) + + def test_no_privkeys(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Migrating an actual watchonly wallet should not create a new watchonly wallet + self.log.info("Test migration of a pure watchonly wallet") + self.nodes[0].createwallet(wallet_name="watchonly0", disable_private_keys=True) + watchonly0 = self.nodes[0].get_wallet_rpc("watchonly0") + info = watchonly0.getwalletinfo() + assert_equal(info["descriptors"], False) + assert_equal(info["private_keys_enabled"], False) + + addr = default.getnewaddress() + desc = default.getaddressinfo(addr)["desc"] + res = watchonly0.importmulti([ + { + "desc": desc, + "watchonly": True, + "timestamp": "now", + }]) + assert_equal(res[0]['success'], True) + default.sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + + watchonly0.migratewallet() + assert_equal("watchonly0_watchonly" in self.nodes[0].listwallets(), False) + info = watchonly0.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["private_keys_enabled"], False) + self.assert_is_sqlite("watchonly0") + + # Migrating a wallet with pubkeys added to the keypool + self.log.info("Test migration of a pure watchonly wallet with pubkeys in keypool") + self.nodes[0].createwallet(wallet_name="watchonly1", disable_private_keys=True) + watchonly1 = self.nodes[0].get_wallet_rpc("watchonly1") + info = watchonly1.getwalletinfo() + assert_equal(info["descriptors"], False) + assert_equal(info["private_keys_enabled"], False) + + addr1 = default.getnewaddress(address_type="bech32") + addr2 = default.getnewaddress(address_type="bech32") + desc1 = default.getaddressinfo(addr1)["desc"] + desc2 = default.getaddressinfo(addr2)["desc"] + res = watchonly1.importmulti([ + { + "desc": desc1, + "keypool": True, + "timestamp": "now", + }, + { + "desc": desc2, + "keypool": True, + "timestamp": "now", + } + ]) + assert_equal(res[0]["success"], True) + assert_equal(res[1]["success"], True) + # Before migrating, we can fetch addr1 from the keypool + assert_equal(watchonly1.getnewaddress(address_type="bech32"), addr1) + + watchonly1.migratewallet() + info = watchonly1.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["private_keys_enabled"], False) + self.assert_is_sqlite("watchonly1") + # After migrating, the "keypool" is empty + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", watchonly1.getnewaddress) + + def test_pk_coinbases(self): + self.log.info("Test migration of a wallet using old pk() coinbases") + wallet = self.create_legacy_wallet("pkcb") + + addr = wallet.getnewaddress() + addr_info = wallet.getaddressinfo(addr) + desc = descsum_create("pk(" + addr_info["pubkey"] + ")") + + self.nodes[0].generatetodescriptor(1, desc, invalid_call=False) + + bals = wallet.getbalances() + + wallet.migratewallet() + + assert_equal(bals, wallet.getbalances()) + + def run_test(self): + self.generate(self.nodes[0], 101) + + # TODO: Test the actual records in the wallet for these tests too. The behavior may be correct, but the data written may not be what we actually want + self.test_basic() + self.test_multisig() + self.test_other_watchonly() + self.test_no_privkeys() + self.test_pk_coinbases() + +if __name__ == '__main__': + WalletMigrationTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 99e472a7b4..1c890d7207 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -356,7 +356,7 @@ class MultiWalletTest(BitcoinTestFramework): self.log.info("Test dynamic wallet unloading") # Test `unloadwallet` errors - assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet) assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy") assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet) assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"), diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 6552bfe60c..b3d02fbfc9 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -9,10 +9,13 @@ from test_framework.blocktools import ( create_block, create_coinbase, ) +from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal - +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) class ResendWalletTransactionsTest(BitcoinTestFramework): def set_test_params(self): @@ -27,13 +30,9 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): peer_first = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a new transaction and wait until it's broadcast") - txid = node.sendtoaddress(node.getnewaddress(), 1) - - # Wallet rebroadcast is first scheduled 1 sec after startup (see - # nNextResend in ResendWalletTransactions()). Tell scheduler to call - # MaybeResendWalletTxn now to initialize nNextResend before the first - # setmocktime call below. - node.mockscheduler(1) + parent_utxo, indep_utxo = node.listunspent()[:2] + addr = node.getnewaddress() + txid = node.send(outputs=[{addr: 1}], options={"inputs": [parent_utxo]})["txid"] # Can take a few seconds due to transaction trickling peer_first.wait_for_broadcast([txid]) @@ -51,7 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): block.solve() node.submitblock(block.serialize().hex()) - # Set correct m_best_block_time, which is used in ResendWalletTransactions + # Set correct m_best_block_time, which is used in ResubmitWalletTransactions node.syncwithvalidationinterfacequeue() now = int(time.time()) @@ -60,20 +59,67 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): twelve_hrs = 12 * 60 * 60 two_min = 2 * 60 node.setmocktime(now + twelve_hrs - two_min) - node.mockscheduler(1) # Tell scheduler to call MaybeResendWalletTxn now + node.mockscheduler(60) # Tell scheduler to call MaybeResendWalletTxs now assert_equal(int(txid, 16) in peer_second.get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. - with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']): + with node.assert_debug_log(['resubmit 1 unconfirmed transactions']): node.setmocktime(now + 36 * 60 * 60) - # Tell scheduler to call MaybeResendWalletTxn now. - node.mockscheduler(1) + # Tell scheduler to call MaybeResendWalletTxs now. + node.mockscheduler(60) # Give some time for trickle to occur node.setmocktime(now + 36 * 60 * 60 + 600) peer_second.wait_for_broadcast([txid]) + self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast") + # This tests that the node broadcasts the parent transaction before the child transaction. + # To test that scenario, we need a method to reliably get a child transaction placed + # in mapWallet positioned before the parent. We cannot predict the position in mapWallet, + # but we can observe it using listreceivedbyaddress and other related RPCs. + # + # So we will create the child transaction, use listreceivedbyaddress to see what the + # ordering of mapWallet is, if the child is not before the parent, we will create a new + # child (via bumpfee) and remove the old child (via removeprunedfunds) until we get the + # ordering of child before parent. + child_txid = node.send(outputs=[{addr: 0.5}], options={"inputs": [{"txid":txid, "vout":0}]})["txid"] + while True: + txids = node.listreceivedbyaddress(minconf=0, address_filter=addr)[0]["txids"] + if txids == [child_txid, txid]: + break + bumped = node.bumpfee(child_txid) + # The scheduler queue creates a copy of the added tx after + # send/bumpfee and re-adds it to the wallet (undoing the next + # removeprunedfunds). So empty the scheduler queue: + node.syncwithvalidationinterfacequeue() + node.removeprunedfunds(child_txid) + child_txid = bumped["txid"] + entry_time = node.getmempoolentry(child_txid)["time"] + + block_time = entry_time + 6 * 60 + node.setmocktime(block_time) + block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time) + block.solve() + node.submitblock(block.serialize().hex()) + # Set correct m_best_block_time, which is used in ResubmitWalletTransactions + node.syncwithvalidationinterfacequeue() + + # Evict these txs from the mempool + evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5 + node.setmocktime(evict_time) + indep_send = node.send(outputs=[{node.getnewaddress(): 1}], options={"inputs": [indep_utxo]}) + node.getmempoolentry(indep_send["txid"]) + assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid) + assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid) + + # Rebroadcast and check that parent and child are both in the mempool + with node.assert_debug_log(['resubmit 2 unconfirmed transactions']): + node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer + node.mockscheduler(60) + node.getmempoolentry(txid) + node.getmempoolentry(child_txid) + if __name__ == '__main__': ResendWalletTransactionsTest().main() diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py index aa8d2a9d2c..db4f32fe16 100755 --- a/test/functional/wallet_sendall.py +++ b/test/functional/wallet_sendall.py @@ -264,6 +264,32 @@ class SendallTest(BitcoinTestFramework): recipients=[self.remainder_target], options={"inputs": [utxo], "send_max": True}) + @cleanup + def sendall_fails_on_high_fee(self): + self.log.info("Test sendall fails if the transaction fee exceeds the maxtxfee") + self.add_utxos([21]) + + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by user", + self.wallet.sendall, + recipients=[self.remainder_target], + fee_rate=100000) + + # This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error + def sendall_fails_with_transaction_too_large(self): + self.log.info("Test that sendall fails if resulting transaction is too large") + # create many inputs + outputs = {self.wallet.getnewaddress(): 0.000025 for _ in range(1600)} + self.def_wallet.sendmany(amounts=outputs) + self.generate(self.nodes[0], 1) + + assert_raises_rpc_error( + -4, + "Transaction too large.", + self.wallet.sendall, + recipients=[self.remainder_target]) + def run_test(self): self.nodes[0].createwallet("activewallet") self.wallet = self.nodes[0].get_wallet_rpc("activewallet") @@ -312,5 +338,11 @@ class SendallTest(BitcoinTestFramework): # Sendall fails when using send_max while specifying inputs self.sendall_fails_on_specific_inputs_with_send_max() + # Sendall fails when providing a fee that is too high + self.sendall_fails_on_high_fee() + + # Sendall fails when many inputs result to too large transaction + self.sendall_fails_with_transaction_too_large() + if __name__ == '__main__': SendallTest().main() diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py index 21941084a3..9caa1fa3d0 100755 --- a/test/functional/wallet_transactiontime_rescan.py +++ b/test/functional/wallet_transactiontime_rescan.py @@ -11,6 +11,7 @@ from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, set_node_times, ) @@ -158,5 +159,11 @@ class TransactionTimeRescanTest(BitcoinTestFramework): assert_equal(tx['time'], cur_time + ten_days + ten_days + ten_days) + self.log.info('Test handling of invalid parameters for rescanblockchain') + assert_raises_rpc_error(-8, "Invalid start_height", restorewo_wallet.rescanblockchain, -1, 10) + assert_raises_rpc_error(-8, "Invalid stop_height", restorewo_wallet.rescanblockchain, 1, -1) + assert_raises_rpc_error(-8, "stop_height must be greater than start_height", restorewo_wallet.rescanblockchain, 20, 10) + + if __name__ == '__main__': TransactionTimeRescanTest().main() diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index a0f17ac119..abf38ca79b 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -14,6 +14,7 @@ import sys EXPECTED_CIRCULAR_DEPENDENCIES = ( "chainparamsbase -> util/system -> chainparamsbase", "node/blockstorage -> validation -> node/blockstorage", + "node/utxo_snapshot -> validation -> node/utxo_snapshot", "policy/fees -> txmempool -> policy/fees", "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel", diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py index afdca0d418..b3fa4b9303 100755 --- a/test/lint/lint-includes.py +++ b/test/lint/lint-includes.py @@ -21,8 +21,7 @@ EXCLUDED_DIRS = ["src/leveldb/", "src/minisketch/", ] -EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string/replace.hpp", - "boost/date_time/posix_time/posix_time.hpp", +EXPECTED_BOOST_INCLUDES = ["boost/date_time/posix_time/posix_time.hpp", "boost/multi_index/hashed_index.hpp", "boost/multi_index/ordered_index.hpp", "boost/multi_index/sequenced_index.hpp", diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py index 4876ac2e2d..ce7444cd1a 100755 --- a/test/lint/lint-locale-dependence.py +++ b/test/lint/lint-locale-dependence.py @@ -250,7 +250,7 @@ def main(): exit_code = 1 if exit_code == 1: - print("Unnecessary locale depedence can cause bugs that are very tricky to isolate and fix. Please avoid using locale dependent functions if possible.\n") + print("Unnecessary locale dependence can cause bugs that are very tricky to isolate and fix. Please avoid using locale-dependent functions if possible.\n") print(f"Advice not applicable in this specific case? Add an exception by updating the ignore list in {sys.argv[0]}") sys.exit(exit_code) diff --git a/test/lint/spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt index 0efd298408..82f18010c1 100644 --- a/test/lint/spelling.ignore-words.txt +++ b/test/lint/spelling.ignore-words.txt @@ -1,21 +1,23 @@ asend ba blockin +bu cachable -creat -desig +clen fo fpr hights -hist -inout +inflight invokable keypair mor nd nin ser +siz +stap unparseable unser useable +warmup wit diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan index 3acf575d07..d331991273 100644 --- a/test/sanitizer_suppressions/tsan +++ b/test/sanitizer_suppressions/tsan @@ -13,7 +13,7 @@ race:zmq::* race:bitcoin-qt # deadlock (TODO fix) -deadlock:CChainState::ConnectTip +deadlock:Chainstate::ConnectTip # Intentional deadlock in tests deadlock:sync_tests::potential_deadlock_detected |