diff options
Diffstat (limited to 'test/functional')
21 files changed, 407 insertions, 33 deletions
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index eff4d9b149..4f8541a5d7 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -156,9 +156,10 @@ class CoinStatsIndexTest(BitcoinTestFramework): # Generate and send another tx with an OP_RETURN output (which is unspendable) tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx'] - tx2.vout = [CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx2_val = '20.99' + tx2.vout = [CTxOut(int(Decimal(tx2_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] tx2_hex = tx2.serialize().hex() - self.nodes[0].sendrawtransaction(tx2_hex) + self.nodes[0].sendrawtransaction(tx2_hex, 0, tx2_val) # Include both txs in a block self.generate(self.nodes[0], 1) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index a37d614535..f9730b48c5 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -251,11 +251,43 @@ class ConfArgsTest(BitcoinTestFramework): ]): self.nodes[0].setmocktime(start + 65) + def test_connect_with_seednode(self): + self.log.info('Test -connect with -seednode') + seednode_ignored = ['-seednode is ignored when -connect is used\n'] + dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n'] + addcon_thread_started = ['addcon thread start\n'] + self.stop_node(0) + + # When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode) + # nodes is irrelevant and -seednode is ignored. + with self.nodes[0].assert_debug_log(expected_msgs=seednode_ignored): + self.start_node(0, extra_args=['-connect=fakeaddress1', '-seednode=fakeaddress2']) + + # With -proxy, an ADDR_FETCH connection is made to a peer that the dns seed resolves to. + # ADDR_FETCH connections are not used when -connect is used. + with self.nodes[0].assert_debug_log(expected_msgs=dnsseed_ignored): + self.restart_node(0, extra_args=['-connect=fakeaddress1', '-dnsseed=1', '-proxy=1.2.3.4']) + + # If the user did not disable -dnsseed, but it was soft-disabled because they provided -connect, + # they shouldn't see a warning about -dnsseed being ignored. + with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started, + unexpected_msgs=dnsseed_ignored): + self.restart_node(0, extra_args=['-connect=fakeaddress1', '-proxy=1.2.3.4']) + + # We have to supply expected_msgs as it's a required argument + # The expected_msg must be something we are confident will be logged after the unexpected_msg + # These cases test for -connect being supplied but only to disable it + for connect_arg in ['-connect=0', '-noconnect']: + with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started, + unexpected_msgs=seednode_ignored): + self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2']) + def run_test(self): self.test_log_buffer() self.test_args_log() self.test_seed_peers() self.test_networkactive() + self.test_connect_with_seednode() self.test_config_file_parser() self.test_invalid_command_line_options() diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index e2bc566f53..1f2e0936ed 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -85,7 +85,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.nodes[node_index].waitforblock(expected_tip) utxo_hash = self.nodes[node_index].gettxoutsetinfo()['hash_serialized_2'] return utxo_hash - except: + except Exception: # An exception here should mean the node is about to crash. # If bitcoind exits, then try again. wait_for_node_exit() # should raise an exception if bitcoind doesn't exit. diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 664ed779db..519877ac5b 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -223,8 +223,8 @@ class PruneTest(BitcoinTestFramework): def reorg_back(self): # Verify that a block on the old main chain fork has been pruned away assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) - with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']): - self.nodes[2].verifychain(checklevel=4, nblocks=0) + with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(no data)']): + assert not self.nodes[2].verifychain(checklevel=4, nblocks=0) self.log.info(f"Will need to redownload block {self.forkheight}") # Verify that we have enough history to reorg back to the fork point diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 144e01c367..8ac06f570d 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -750,7 +750,7 @@ def spenders_taproot_active(): # Reusing the scripts above, test that various features affect the sighash. add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=False, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/script", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, script_taproot=tap.leaves["codesep_pk"].script)}, **ERR_SIG_SCHNORR) - add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != 0xC0]))}, **ERR_SIG_SCHNORR) + add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != LEAF_VERSION_TAPSCRIPT]))}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leaf=None)}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/keypath", tap=tap, key=secs[0], **common, failure={"sighash": override(default_sighash, leaf="pk_codesep")}, **ERR_SIG_SCHNORR) @@ -1555,12 +1555,16 @@ class TaprootTest(BitcoinTestFramework): script_lists = [ None, - [("0", CScript([pubs[50], OP_CHECKSIG]), 0xc0)], - [("0", CScript([pubs[51], OP_CHECKSIG]), 0xc0)], - [("0", CScript([pubs[52], OP_CHECKSIG]), 0xc0), ("1", CScript([b"BIP341"]), VALID_LEAF_VERS[pubs[99][0] % 41])], - [("0", CScript([pubs[53], OP_CHECKSIG]), 0xc0), ("1", CScript([b"Taproot"]), VALID_LEAF_VERS[pubs[99][1] % 41])], - [("0", CScript([pubs[54], OP_CHECKSIG]), 0xc0), [("1", CScript([pubs[55], OP_CHECKSIG]), 0xc0), ("2", CScript([pubs[56], OP_CHECKSIG]), 0xc0)]], - [("0", CScript([pubs[57], OP_CHECKSIG]), 0xc0), [("1", CScript([pubs[58], OP_CHECKSIG]), 0xc0), ("2", CScript([pubs[59], OP_CHECKSIG]), 0xc0)]], + [("0", CScript([pubs[50], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)], + [("0", CScript([pubs[51], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)], + [("0", CScript([pubs[52], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("1", CScript([b"BIP341"]), VALID_LEAF_VERS[pubs[99][0] % 41])], + [("0", CScript([pubs[53], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("1", CScript([b"Taproot"]), VALID_LEAF_VERS[pubs[99][1] % 41])], + [("0", CScript([pubs[54], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), + [("1", CScript([pubs[55], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("2", CScript([pubs[56], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)] + ], + [("0", CScript([pubs[57], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), + [("1", CScript([pubs[58], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("2", CScript([pubs[59], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)] + ], ] taps = [taproot_construct(inner_keys[i], script_lists[i]) for i in range(len(inner_keys))] diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index b2f0659eda..394009f30f 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -116,7 +116,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("disconnectnode: successfully disconnect node by address") address1 = self.nodes[0].getpeerinfo()[0]['addr'] self.nodes[0].disconnectnode(address=address1) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10) + self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10) assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1] self.log.info("disconnectnode: successfully reconnect node") @@ -127,7 +127,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("disconnectnode: successfully disconnect node by node id") id1 = self.nodes[0].getpeerinfo()[0]['id'] self.nodes[0].disconnectnode(nodeid=id1) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10) + self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10) assert not [node for node in self.nodes[0].getpeerinfo() if node['id'] == id1] if __name__ == '__main__': diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 5a0003d3ef..a56afbcf7b 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -85,7 +85,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): self.connect_nodes(0, 2) try: self.sync_blocks([self.nodes[0], self.nodes[2]], timeout=5) - except: + except Exception: pass # node2 must remain at height 0 assert_equal(self.nodes[2].getblockheader(self.nodes[2].getbestblockhash())['height'], 0) diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 8df3b68785..673836bd04 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -272,6 +272,9 @@ class DecodeScriptTest(BitcoinTestFramework): # Miniscript-incompatible offered HTLC res = self.nodes[0].decodescript("82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2") assert res["segwit"]["desc"] == "wsh(raw(82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2))#ra6w2xa7" + # Miniscript-compatible multisig bigger than 520 byte P2SH limit. + res = self.nodes[0].decodescript("5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68") + assert_equal(res["segwit"]["desc"], "wsh(or_d(multi(11,020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb,03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a),and_v(v:older(4032),multi(2,03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb))))#7jwwklk4") def run_test(self): self.log.info("Test decoding of standard input scripts [scriptSig]") diff --git a/test/functional/rpc_preciousblock.py b/test/functional/rpc_preciousblock.py index 91298937fd..3062a86565 100755 --- a/test/functional/rpc_preciousblock.py +++ b/test/functional/rpc_preciousblock.py @@ -16,7 +16,7 @@ def unidirectional_node_sync_via_rpc(node_src, node_dest): try: assert len(node_dest.getblock(blockhash, False)) > 0 break - except: + except Exception: blocks_to_copy.append(blockhash) blockhash = node_src.getblockheader(blockhash, True)['previousblockhash'] blocks_to_copy.reverse() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index cdec4b2a85..2395935620 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -18,9 +18,17 @@ from itertools import product from test_framework.messages import ( MAX_BIP125_RBF_SEQUENCE, + COIN, CTransaction, + CTxOut, tx_from_hex, ) +from test_framework.script import ( + CScript, + OP_FALSE, + OP_INVALIDOPCODE, + OP_RETURN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -331,6 +339,57 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx) + self.log.info("Test sendrawtransaction exceeding, falling short of, and equaling maxburnamount") + max_burn_exceeded = "Unspendable output exceeds maximum configured by user (maxburnamount)" + + + # Test that spendable transaction with default maxburnamount (0) gets sent + tx = self.wallet.create_self_transfer()['tx'] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex) + + # Test that datacarrier transaction with default maxburnamount (0) does not get sent + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test that oversized script gets rejected by sendrawtransaction + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_FALSE] * 10001))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test that script containing invalid opcode gets rejected by sendrawtransaction + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.01 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_INVALIDOPCODE]))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test a transaction where our burn exceeds maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex, 0, 0.0009) + + # Test a transaction where our burn falls short of maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex, maxfeerate='0', maxburnamount='0.0011') + + # Test a transaction where our burn equals maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex, maxfeerate='0', maxburnamount='0.001') + def sendrawtransaction_testmempoolaccept_tests(self): self.log.info("Test sendrawtransaction/testmempoolaccept with maxfeerate") fee_exceeds_max = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)" diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py index dd20b28550..61f92aeac3 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -78,7 +78,10 @@ class AuthServiceProxy(): passwd = None if self.__url.password is None else self.__url.password.encode('utf8') authpair = user + b':' + passwd self.__auth_header = b'Basic ' + base64.b64encode(authpair) - self.timeout = timeout + # clamp the socket timeout, since larger values can cause an + # "Invalid argument" exception in Python's HTTP(S) client + # library on some operating systems (e.g. OpenBSD, FreeBSD) + self.timeout = min(timeout, 2147483) self._set_conn(connection) def __getattr__(self, name): diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 59157f4755..c5768177bd 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -385,7 +385,7 @@ class P2PInterface(P2PConnection): self.message_count[msgtype] += 1 self.last_message[msgtype] = message getattr(self, 'on_' + msgtype)(message) - except: + except Exception: print("ERROR delivering %s (%s)" % (repr(message), sys.exc_info()[0])) raise diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 513b795478..9620951a16 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -279,10 +279,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if seed is None: seed = random.randrange(sys.maxsize) else: - self.log.debug("User supplied random seed {}".format(seed)) + self.log.info("User supplied random seed {}".format(seed)) random.seed(seed) - self.log.debug("PRNG seed is: {}".format(seed)) + self.log.info("PRNG seed is: {}".format(seed)) self.log.debug('Setting up network thread') self.network_thread = NetworkThread() @@ -557,7 +557,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): node.start(extra_args[i], *args, **kwargs) for node in self.nodes: node.wait_for_rpc_connection() - except: + except Exception: # If one node failed to start, stop the others self.stop_nodes() raise diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index b515538a1a..882f82e0f2 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -829,7 +829,7 @@ class RPCOverloadWrapper(): int(address ,16) is_hex = True desc = descsum_create('raw(' + address + ')') - except: + except Exception: desc = descsum_create('addr(' + address + ')') reqs = [{ 'desc': desc, diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 0fe28b3855..26ebce039b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -216,6 +216,8 @@ BASE_SCRIPTS = [ 'rpc_blockchain.py', 'rpc_deprecated.py', 'wallet_disable.py', + 'wallet_change_address.py --legacy-wallet', + 'wallet_change_address.py --descriptors', 'p2p_addr_relay.py', 'p2p_getaddr_caching.py', 'p2p_getdata.py', diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index a2ae997ecb..ad79e0288c 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -81,7 +81,7 @@ class BumpFeeTest(BitcoinTestFramework): self.log.info("Running tests") dest_address = peer_node.getnewaddress() - for mode in ["default", "fee_rate"]: + for mode in ["default", "fee_rate", "new_outputs"]: test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address) self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) @@ -157,6 +157,14 @@ class BumpFeeTest(BitcoinTestFramework): assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', rbf_node.bumpfee, rbfid, {"estimate_mode": mode}) + self.log.info("Test invalid outputs values") + assert_raises_rpc_error(-8, "Invalid parameter, output argument cannot be an empty array", + rbf_node.bumpfee, rbfid, {"outputs": []}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: " + dest_address, + rbf_node.bumpfee, rbfid, {"outputs": [{dest_address: 0.1}, {dest_address: 0.2}]}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", + rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]}) + self.clear_mempool() @@ -169,6 +177,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): if mode == "fee_rate": bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": str(NORMAL)}) bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL}) + elif mode == "new_outputs": + new_address = peer_node.getnewaddress() + bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"outputs": {new_address: 0.0003}}) + bumped_tx = rbf_node.bumpfee(rbfid, {"outputs": {new_address: 0.0003}}) else: bumped_psbt = rbf_node.psbtbumpfee(rbfid) bumped_tx = rbf_node.bumpfee(rbfid) @@ -192,6 +204,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"]) assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"]) assert_equal(bumpedwtx["replaces_txid"], rbfid) + # if this is a new_outputs test, check that outputs were indeed replaced + if mode == "new_outputs": + assert len(bumpedwtx["details"]) == 1 + assert bumpedwtx["details"][0]["address"] == new_address self.clear_mempool() @@ -628,12 +644,14 @@ def test_change_script_match(self, rbf_node, dest_address): self.clear_mempool() -def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000"), data=None): tx_input = dict( sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) destinations = {dest_address: Decimal("0.00050000")} if change_size > 0: destinations[node.getrawchangeaddress()] = change_size + if data: + destinations['data'] = data rawtx = node.createrawtransaction([tx_input], destinations) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx["hex"]) diff --git a/test/functional/wallet_change_address.py b/test/functional/wallet_change_address.py new file mode 100755 index 0000000000..f8bfe9eebf --- /dev/null +++ b/test/functional/wallet_change_address.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test wallet change address selection""" + +import re + +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class WalletChangeAddressTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + # discardfee is used to make change outputs less likely in the change_pos test + self.extra_args = [ + [], + ["-discardfee=1"], + ["-avoidpartialspends", "-discardfee=1"] + ] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def assert_change_index(self, node, tx, index): + change_index = None + for vout in tx["vout"]: + info = node.getaddressinfo(vout["scriptPubKey"]["address"]) + if (info["ismine"] and info["ischange"]): + change_index = int(re.findall(r'\d+', info["hdkeypath"])[-1]) + break + assert_equal(change_index, index) + + def assert_change_pos(self, wallet, tx, pos): + change_pos = None + for index, output in enumerate(tx["vout"]): + info = wallet.getaddressinfo(output["scriptPubKey"]["address"]) + if (info["ismine"] and info["ischange"]): + change_pos = index + break + assert_equal(change_pos, pos) + + def run_test(self): + self.log.info("Setting up") + # Mine some coins + self.generate(self.nodes[0], COINBASE_MATURITY + 1) + + # Get some addresses from the two nodes + addr1 = [self.nodes[1].getnewaddress() for _ in range(3)] + addr2 = [self.nodes[2].getnewaddress() for _ in range(3)] + addrs = addr1 + addr2 + + # Send 1 + 0.5 coin to each address + [self.nodes[0].sendtoaddress(addr, 1.0) for addr in addrs] + [self.nodes[0].sendtoaddress(addr, 0.5) for addr in addrs] + self.generate(self.nodes[0], 1) + + for i in range(20): + for n in [1, 2]: + self.log.debug(f"Send transaction from node {n}: expected change index {i}") + txid = self.nodes[n].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) + tx = self.nodes[n].getrawtransaction(txid, True) + # find the change output and ensure that expected change index was used + self.assert_change_index(self.nodes[n], tx, i) + + # Start next test with fresh wallets and new coins + self.nodes[1].createwallet("w1") + self.nodes[2].createwallet("w2") + w1 = self.nodes[1].get_wallet_rpc("w1") + w2 = self.nodes[2].get_wallet_rpc("w2") + addr1 = w1.getnewaddress() + addr2 = w2.getnewaddress() + self.nodes[0].sendtoaddress(addr1, 3.0) + self.nodes[0].sendtoaddress(addr1, 0.1) + self.nodes[0].sendtoaddress(addr2, 3.0) + self.nodes[0].sendtoaddress(addr2, 0.1) + self.generate(self.nodes[0], 1) + + sendTo1 = self.nodes[0].getnewaddress() + sendTo2 = self.nodes[0].getnewaddress() + sendTo3 = self.nodes[0].getnewaddress() + + # The avoid partial spends wallet will always create a change output + node = self.nodes[2] + res = w2.send({sendTo1: "1.0", sendTo2: "1.0", sendTo3: "0.9999"}, options={"change_position": 0}) + tx = node.getrawtransaction(res["txid"], True) + self.assert_change_pos(w2, tx, 0) + + # The default wallet will internally create a tx without change first, + # then create a second candidate using APS that requires a change output. + # Ensure that the user-configured change position is kept + node = self.nodes[1] + res = w1.send({sendTo1: "1.0", sendTo2: "1.0", sendTo3: "0.9999"}, options={"change_position": 0}) + tx = node.getrawtransaction(res["txid"], True) + # If the wallet ignores the user's change_position there is still a 25% + # that the random change position passes the test + self.assert_change_pos(w1, tx, 0) + +if __name__ == '__main__': + WalletChangeAddressTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 885c52cf2e..88b9ebbddd 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -90,6 +90,17 @@ class WalletEncryptionTest(BitcoinTestFramework): self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] assert_equal(actual_time, expected_time) + self.nodes[0].walletlock() + + # Test passphrase with null characters + passphrase_with_nulls = "Phrase\0With\0Nulls" + self.nodes[0].walletpassphrasechange(passphrase2, passphrase_with_nulls) + # walletpassphrasechange should not stop at null characters + assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase_with_nulls.partition("\0")[0], 10) + self.nodes[0].walletpassphrase(passphrase_with_nulls, 10) + sig = self.nodes[0].signmessage(address, msg) + assert self.nodes[0].verifymessage(address, sig, msg) + self.nodes[0].walletlock() if __name__ == '__main__': diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index ca0209b61d..e66eb2c289 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -448,14 +448,14 @@ class ImportDescriptorsTest(BitcoinTestFramework): wallet=wmulti_priv) assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) # Range end (1000) is inclusive, so 1001 addresses generated - addr = wmulti_priv.getnewaddress('', 'bech32') + addr = wmulti_priv.getnewaddress('', 'bech32') # uses receive 0 assert_equal(addr, 'bcrt1qdt0qy5p7dzhxzmegnn4ulzhard33s2809arjqgjndx87rv5vd0fq2czhy8') # Derived at m/84'/0'/0'/0 - change_addr = wmulti_priv.getrawchangeaddress('bech32') - assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') + change_addr = wmulti_priv.getrawchangeaddress('bech32') # uses change 0 + assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') # Derived at m/84'/1'/0'/0 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000) txid = w0.sendtoaddress(addr, 10) self.generate(self.nodes[0], 6) - send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) + send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) # uses change 1 decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded'] assert_equal(len(decoded['vin'][0]['txinwitness']), 4) self.sync_all() @@ -481,10 +481,10 @@ class ImportDescriptorsTest(BitcoinTestFramework): wallet=wmulti_pub) assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) # The first one was already consumed by previous import and is detected as used - addr = wmulti_pub.getnewaddress('', 'bech32') + addr = wmulti_pub.getnewaddress('', 'bech32') # uses receive 1 assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1 - change_addr = wmulti_pub.getrawchangeaddress('bech32') - assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl') + change_addr = wmulti_pub.getrawchangeaddress('bech32') # uses change 2 + assert_equal(change_addr, 'bcrt1qp6j3jw8yetefte7kw6v5pc89rkgakzy98p6gf7ayslaveaxqyjusnw580c') # Derived at m/84'/1'/0'/2 assert send_txid in self.nodes[0].getrawmempool(True) assert send_txid in (x['txid'] for x in wmulti_pub.listunspent(0)) assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) @@ -667,5 +667,33 @@ class ImportDescriptorsTest(BitcoinTestFramework): success=True, warnings=["Unknown output type, cannot set descriptor to active."]) + self.log.info("Test importing a descriptor to an encrypted wallet") + + descriptor = {"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"), + "timestamp": "now", + "active": True, + "range": [0,4000], + "next_index": 4000} + + self.nodes[0].createwallet("temp_wallet", blank=True, descriptors=True) + temp_wallet = self.nodes[0].get_wallet_rpc("temp_wallet") + temp_wallet.importdescriptors([descriptor]) + self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + + self.nodes[0].createwallet("encrypted_wallet", blank=True, descriptors=True, passphrase="passphrase") + encrypted_wallet = self.nodes[0].get_wallet_rpc("encrypted_wallet") + + descriptor["timestamp"] = 0 + descriptor["next_index"] = 0 + + batch = [] + batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3)) + batch.append(encrypted_wallet.importdescriptors.get_request([descriptor])) + + encrypted_wallet.batch(batch) + + assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance()) + if __name__ == '__main__': ImportDescriptorsTest().main() diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 72c5fe7b84..7c2959bb89 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -400,11 +400,75 @@ class WalletMigrationTest(BitcoinTestFramework): def test_encrypted(self): self.log.info("Test migration of an encrypted wallet") wallet = self.create_legacy_wallet("encrypted") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) wallet.encryptwallet("pass") + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet) + assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet, None, "badpass") + assert_raises_rpc_error(-4, "The passphrase contains a null character", wallet.migratewallet, None, "pass\0with\0null") + + wallet.migratewallet(passphrase="pass") + + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + assert_equal(info["unlocked_until"], 0) + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) + + def test_unloaded(self): + self.log.info("Test migration of a wallet that isn't loaded") + wallet = self.create_legacy_wallet("notloaded") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + wallet.unloadwallet() + + assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", wallet.migratewallet, "someotherwallet") + assert_raises_rpc_error(-8, "Either RPC endpoint wallet or wallet_name parameter must be provided", self.nodes[0].migratewallet) + self.nodes[0].migratewallet("notloaded") - assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet) - # TODO: Fix migratewallet so that we can actually migrate encrypted wallets + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) + + def test_unloaded_by_path(self): + self.log.info("Test migration of a wallet that isn't loaded, specified by path") + wallet = self.create_legacy_wallet("notloaded2") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + wallet.unloadwallet() + + wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest", "wallets", "notloaded2") + self.nodes[0].migratewallet(wallet_file_path) + + # Because we gave the name by full path, the loaded wallet's name is that path too. + wallet = self.nodes[0].get_wallet_rpc(wallet_file_path) + + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) def run_test(self): self.generate(self.nodes[0], 101) @@ -416,6 +480,8 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_no_privkeys() self.test_pk_coinbases() self.test_encrypted() + self.test_unloaded() + self.test_unloaded_by_path() if __name__ == '__main__': WalletMigrationTest().main() diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py index de9616b4a1..904013cdef 100755 --- a/test/functional/wallet_transactiontime_rescan.py +++ b/test/functional/wallet_transactiontime_rescan.py @@ -14,6 +14,9 @@ from test_framework.util import ( assert_raises_rpc_error, set_node_times, ) +from test_framework.wallet_util import ( + get_generate_key, +) class TransactionTimeRescanTest(BitcoinTestFramework): @@ -23,6 +26,10 @@ class TransactionTimeRescanTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 + self.extra_args = [["-keypool=400"], + ["-keypool=400"], + [] + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -167,6 +174,38 @@ class TransactionTimeRescanTest(BitcoinTestFramework): 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) + self.log.info("Test `rescanblockchain` fails when wallet is encrypted and locked") + usernode.createwallet(wallet_name="enc_wallet", passphrase="passphrase") + enc_wallet = usernode.get_wallet_rpc("enc_wallet") + assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", enc_wallet.rescanblockchain) + + if not self.options.descriptors: + self.log.info("Test rescanning an encrypted wallet") + hd_seed = get_generate_key().privkey + + usernode.createwallet(wallet_name="temp_wallet", blank=True, descriptors=False) + temp_wallet = usernode.get_wallet_rpc("temp_wallet") + temp_wallet.sethdseed(seed=hd_seed) + + for i in range(399): + temp_wallet.getnewaddress() + + self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + + minernode.createwallet("encrypted_wallet", blank=True, passphrase="passphrase", descriptors=False) + encrypted_wallet = minernode.get_wallet_rpc("encrypted_wallet") + + encrypted_wallet.walletpassphrase("passphrase", 1) + encrypted_wallet.sethdseed(seed=hd_seed) + + batch = [] + batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3)) + batch.append(encrypted_wallet.rescanblockchain.get_request()) + + encrypted_wallet.batch(batch) + + assert_equal(encrypted_wallet.getbalance(), temp_wallet.getbalance()) if __name__ == '__main__': TransactionTimeRescanTest().main() |