diff options
Diffstat (limited to 'test/functional')
-rw-r--r-- | test/functional/data/rpc_decodescript.json | 11 | ||||
-rwxr-xr-x | test/functional/feature_maxtipage.py | 56 | ||||
-rwxr-xr-x | test/functional/rpc_fundrawtransaction.py | 53 | ||||
-rwxr-xr-x | test/functional/rpc_getblockfrompeer.py | 16 | ||||
-rwxr-xr-x | test/functional/rpc_psbt.py | 89 | ||||
-rwxr-xr-x | test/functional/rpc_rawtransaction.py | 39 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 2 | ||||
-rwxr-xr-x | test/functional/wallet_send.py | 40 | ||||
-rwxr-xr-x | test/functional/wallet_timelock.py | 50 |
9 files changed, 317 insertions, 39 deletions
diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index d1aa9ab00d..8903f5efac 100644 --- a/test/functional/data/rpc_decodescript.json +++ b/test/functional/data/rpc_decodescript.json @@ -4,6 +4,7 @@ { "asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh", + "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz", "type": "witness_v1_taproot" } ], @@ -12,6 +13,7 @@ { "asm": "1 -28398", "address": "bcrt1pamhqk96edn", + "desc": "addr(bcrt1pamhqk96edn)#vkh8uj5a", "type": "witness_unknown" } ], @@ -20,6 +22,7 @@ { "asm": "0 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t", + "desc": "addr(bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t)#afaecevx", "type": "witness_v0_scripthash", "p2sh": "2MwGk8mw1GBP6U9D5X8gTvgvXpuknmAK3fo" } @@ -29,6 +32,7 @@ { "asm": "OP_HASH160 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee OP_EQUAL", "address": "2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp", + "desc": "addr(2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp)#ywfcpmh9", "type": "scripthash" } ], @@ -36,6 +40,7 @@ "6a00", { "asm": "OP_RETURN 0", + "desc": "raw(6a00)#ncfmkl43", "type": "nulldata" } ], @@ -43,6 +48,7 @@ "6aee", { "asm": "OP_RETURN OP_UNKNOWN", + "desc": "raw(6aee)#vsyzgqdt", "type": "nonstandard" } ], @@ -50,6 +56,7 @@ "6a02ee", { "asm": "OP_RETURN [error]", + "desc": "raw(6a02ee)#gvdwnlzl", "type": "nonstandard" } ], @@ -57,10 +64,12 @@ "02eeee", { "asm": "-28398", + "desc": "raw(02eeee)#5xzck7pr", "type": "nonstandard", "p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM", "segwit": { "asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", + "desc": "addr(bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5)#5akkdska", "hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", "address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5", "type": "witness_v0_scripthash", @@ -72,6 +81,7 @@ "ba", { "asm": "OP_CHECKSIGADD", + "desc": "raw(ba)#yy0eg44l", "type": "nonstandard" } ], @@ -79,6 +89,7 @@ "50", { "asm": "OP_RESERVED", + "desc": "raw(50)#a7tu03xf", "type": "nonstandard" } ] diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py new file mode 100755 index 0000000000..87f9d6962d --- /dev/null +++ b/test/functional/feature_maxtipage.py @@ -0,0 +1,56 @@ +#!/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 logic for setting nMaxTipAge on command line. + +Nodes don't consider themselves out of "initial block download" as long as +their best known block header time is more than nMaxTipAge in the past. +""" + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +DEFAULT_MAX_TIP_AGE = 24 * 60 * 60 + + +class MaxTipAgeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def test_maxtipage(self, maxtipage, set_parameter=True): + node_miner = self.nodes[0] + node_ibd = self.nodes[1] + + self.restart_node(1, [f'-maxtipage={maxtipage}'] if set_parameter else None) + self.connect_nodes(0, 1) + + # tips older than maximum age -> stay in IBD + cur_time = int(time.time()) + node_ibd.setmocktime(cur_time) + for delta in [5, 4, 3, 2, 1]: + node_miner.setmocktime(cur_time - maxtipage - delta) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True) + + # tip within maximum age -> leave IBD + node_miner.setmocktime(cur_time - maxtipage) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False) + + def run_test(self): + self.log.info("Test IBD with maximum tip age of 24 hours (default).") + self.test_maxtipage(DEFAULT_MAX_TIP_AGE, set_parameter=False) + + for hours in [20, 10, 5, 2, 1]: + maxtipage = hours * 60 * 60 + self.log.info(f"Test IBD with maximum tip age of {hours} hours (-maxtipage={maxtipage}).") + self.test_maxtipage(maxtipage) + + +if __name__ == '__main__': + MaxTipAgeTest().main() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index a8e6acea45..759e43194b 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -4,8 +4,10 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the fundrawtransaction RPC.""" + from decimal import Decimal from itertools import product +from math import ceil from test_framework.descriptors import descsum_create from test_framework.key import ECKey @@ -1003,7 +1005,7 @@ class RawTransactionsTest(BitcoinTestFramework): ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): 15}) + raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): ext_utxo["amount"] / 2}) assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx) # Error conditions @@ -1011,6 +1013,12 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["01234567890a0b0c0d0e0f"]}}) assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"scripts":["not a script"]}}) assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, {"solving_data": {"descriptors":["not a descriptor"]}}) + assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"]}]}) + assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": -1}]}) + assert_raises_rpc_error(-8, "Invalid parameter, missing weight key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"]}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 164}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": -1}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be greater than", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 400001}]}) # But funding should work when the solving data is provided funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) @@ -1020,10 +1028,45 @@ class RawTransactionsTest(BitcoinTestFramework): assert signed_tx['complete'] funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}}) - signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex']) - assert not signed_tx['complete'] - signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex']) - assert signed_tx['complete'] + signed_tx1 = wallet.signrawtransactionwithwallet(funded_tx['hex']) + assert not signed_tx1['complete'] + signed_tx2 = self.nodes[0].signrawtransactionwithwallet(signed_tx1['hex']) + assert signed_tx2['complete'] + + unsigned_weight = self.nodes[0].decoderawtransaction(signed_tx1["hex"])["weight"] + signed_weight = self.nodes[0].decoderawtransaction(signed_tx2["hex"])["weight"] + # Input's weight is difference between weight of signed and unsigned, + # and the weight of stuff that didn't change (prevout, sequence, 1 byte of scriptSig) + input_weight = signed_weight - unsigned_weight + (41 * 4) + low_input_weight = input_weight // 2 + high_input_weight = input_weight * 2 + + # Funding should also work if the input weight is provided + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}]}) + signed_tx = wallet.signrawtransactionwithwallet(funded_tx["hex"]) + signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx["hex"]) + assert_equal(self.nodes[0].testmempoolaccept([signed_tx["hex"]])[0]["allowed"], True) + assert_equal(signed_tx["complete"], True) + # Reducing the weight should have a lower fee + funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}]}) + assert_greater_than(funded_tx["fee"], funded_tx2["fee"]) + # Increasing the weight should have a higher fee + funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]}) + assert_greater_than(funded_tx2["fee"], funded_tx["fee"]) + # The provided weight should override the calculated weight when solving data is provided + funded_tx3 = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}, "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]}) + assert_equal(funded_tx2["fee"], funded_tx3["fee"]) + # The feerate should be met + funded_tx4 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], "fee_rate": 10}) + input_add_weight = high_input_weight - (41 * 4) + tx4_weight = wallet.decoderawtransaction(funded_tx4["hex"])["weight"] + input_add_weight + tx4_vsize = int(ceil(tx4_weight / 4)) + assert_fee_amount(funded_tx4["fee"], tx4_vsize, Decimal(0.0001)) + + # Funding with weight at csuint boundaries should not cause problems + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 255}]}) + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 65539}]}) + self.nodes[2].unloadwallet("extfund") def test_include_unsafe(self): diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index effcebe854..b65322d920 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -40,12 +40,8 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.sync_blocks() self.log.info("Node 0 should only have the header for node 1's block 3") - for x in self.nodes[0].getchaintips(): - if x['hash'] == short_tip: - assert_equal(x['status'], "headers-only") - break - else: - raise AssertionError("short tip not synced") + x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips())) + assert_equal(x['status'], "headers-only") assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip) self.log.info("Fetch block from node 1") @@ -60,17 +56,15 @@ class GetBlockFromPeerTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) self.log.info("Non-existent peer generates error") - assert_raises_rpc_error(-1, f"Peer nodeid {peer_0_peer_1_id + 1} does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1) + assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1) self.log.info("Successful fetch") result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) self.wait_until(lambda: self.check_for_block(short_tip), timeout=1) - assert(not "warnings" in result) + assert_equal(result, {}) self.log.info("Don't fetch blocks we already have") - result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) - assert("warnings" in result) - assert_equal(result["warnings"], "Block already downloaded") + assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) if __name__ == '__main__': GetBlockFromPeerTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 153a201e95..b037807b53 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -606,11 +606,15 @@ class PSBTTest(BitcoinTestFramework): assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==') - # Test that we can fund psbts with external inputs specified + self.log.info("Test that we can fund psbts with external inputs specified") + eckey = ECKey() eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) + self.nodes[1].createwallet("extfund") + wallet = self.nodes[1].get_wallet_rpc("extfund") + # Make a weird but signable script. sh(pkh()) descriptor accomplishes this desc = descsum_create("sh(pkh({}))".format(privkey)) if self.options.descriptors: @@ -622,26 +626,97 @@ class PSBTTest(BitcoinTestFramework): addr_info = self.nodes[0].getaddressinfo(addr) self.nodes[0].sendtoaddress(addr, 10) + self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10) self.generate(self.nodes[0], 6) ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[1].walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 10 + ext_utxo['amount']}, 0, {'add_inputs': True}) + assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15}) # But funding should work when the solving data is provided - psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) - signed = self.nodes[1].walletprocesspsbt(psbt['psbt']) + psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) + signed = wallet.walletprocesspsbt(psbt['psbt']) assert not signed['complete'] signed = self.nodes[0].walletprocesspsbt(signed['psbt']) assert signed['complete'] self.nodes[0].finalizepsbt(signed['psbt']) - psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data":{"descriptors": [desc]}}) - signed = self.nodes[1].walletprocesspsbt(psbt['psbt']) + psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data":{"descriptors": [desc]}}) + signed = wallet.walletprocesspsbt(psbt['psbt']) assert not signed['complete'] signed = self.nodes[0].walletprocesspsbt(signed['psbt']) assert signed['complete'] - self.nodes[0].finalizepsbt(signed['psbt']) + final = self.nodes[0].finalizepsbt(signed['psbt'], False) + + dec = self.nodes[0].decodepsbt(signed["psbt"]) + for i, txin in enumerate(dec["tx"]["vin"]): + if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: + input_idx = i + break + psbt_in = dec["inputs"][input_idx] + # Calculate the input weight + # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 + len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 + input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + low_input_weight = input_weight // 2 + high_input_weight = input_weight * 2 + + # Input weight error conditions + assert_raises_rpc_error( + -8, + "Input weights should be specified in inputs rather than in options.", + wallet.walletcreatefundedpsbt, + inputs=[ext_utxo], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]} + ) + + # Funding should also work if the input weight is provided + psbt = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + signed = wallet.walletprocesspsbt(psbt["psbt"]) + signed = self.nodes[0].walletprocesspsbt(signed["psbt"]) + final = self.nodes[0].finalizepsbt(signed["psbt"]) + assert self.nodes[0].testmempoolaccept([final["hex"]])[0]["allowed"] + # Reducing the weight should have a lower fee + psbt2 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_greater_than(psbt["fee"], psbt2["fee"]) + # Increasing the weight should have a higher fee + psbt2 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_greater_than(psbt2["fee"], psbt["fee"]) + # The provided weight should override the calculated weight when solving data is provided + psbt3 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={'add_inputs': True, "solving_data":{"descriptors": [desc]}} + ) + assert_equal(psbt2["fee"], psbt3["fee"]) + + # Import the external utxo descriptor so that we can sign for it from the test wallet + if self.options.descriptors: + res = wallet.importdescriptors([{"desc": desc, "timestamp": "now"}]) + else: + res = wallet.importmulti([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + # The provided weight should override the calculated weight for a wallet input + psbt3 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_equal(psbt2["fee"], psbt3["fee"]) if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 96691b2686..a839af0288 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -99,25 +99,36 @@ class RawTransactionsTest(BitcoinTestFramework): rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999}) rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) - self.generate(self.nodes[0], 1) + self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']]) + err_msg = ( + "No such mempool transaction. Use -txindex or provide a block hash to enable" + " blockchain transaction queries. Use gettransaction for wallet transactions." + ) for n in [0, 3]: self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex") - # 1. valid parameters - only supply txid - assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 2. valid parameters - supply txid and 0 for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) + if n == 0: + # With -txindex. + # 1. valid parameters - only supply txid + assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 3. valid parameters - supply txid and False for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) + # 2. valid parameters - supply txid and 0 for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) - # 4. valid parameters - supply txid and 1 for verbose. - # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. - assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + # 3. valid parameters - supply txid and False for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) - # 5. valid parameters - supply txid and True for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + # 4. valid parameters - supply txid and 1 for verbose. + # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. + assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + + # 5. valid parameters - supply txid and True for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + else: + # Without -txindex, expect to raise. + for verbose in [None, 0, False, 1, True]: + assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txId, verbose) # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose for value in ["True", "False"]: @@ -145,10 +156,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert 'in_active_chain' not in gottx else: self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise") - err_msg = ( - "No such mempool transaction. Use -txindex or provide a block hash to enable" - " blockchain transaction queries. Use gettransaction for wallet transactions." - ) assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True) # 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) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index eb2d030f4a..e833128063 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -198,6 +198,7 @@ BASE_SCRIPTS = [ 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', 'wallet_descriptor.py --descriptors', + 'feature_maxtipage.py', 'p2p_nobloomfilter_messages.py', 'p2p_filter.py', 'rpc_setban.py', @@ -308,6 +309,7 @@ BASE_SCRIPTS = [ 'feature_coinstatsindex.py --legacy-wallet', 'feature_coinstatsindex.py --descriptors', 'wallet_orphanedreward.py', + 'wallet_timelock.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', 'feature_blocksdir.py', diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index d77d554baa..843a9f52b7 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -518,5 +518,45 @@ class WalletSendTest(BitcoinTestFramework): assert signed["complete"] self.nodes[0].finalizepsbt(signed["psbt"]) + dec = self.nodes[0].decodepsbt(signed["psbt"]) + for i, txin in enumerate(dec["tx"]["vin"]): + if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: + input_idx = i + break + psbt_in = dec["inputs"][input_idx] + # Calculate the input weight + # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 + len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 + input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + + # Input weight error conditions + assert_raises_rpc_error( + -8, + "Input weights should be specified in inputs rather than in options.", + ext_wallet.send, + outputs={self.nodes[0].getnewaddress(): 15}, + options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]} + ) + + # Funding should also work when input weights are provided + res = self.test_send( + from_wallet=ext_wallet, + to_wallet=self.nodes[0], + amount=15, + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}], + add_inputs=True, + psbt=True, + include_watching=True, + fee_rate=10 + ) + signed = ext_wallet.walletprocesspsbt(res["psbt"]) + signed = ext_fund.walletprocesspsbt(res["psbt"]) + assert signed["complete"] + tx = self.nodes[0].finalizepsbt(signed["psbt"]) + testres = self.nodes[0].testmempoolaccept([tx["hex"]])[0] + assert_equal(testres["allowed"], True) + assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001)) + if __name__ == '__main__': WalletSendTest().main() diff --git a/test/functional/wallet_timelock.py b/test/functional/wallet_timelock.py new file mode 100755 index 0000000000..cf233a00ef --- /dev/null +++ b/test/functional/wallet_timelock.py @@ -0,0 +1,50 @@ +#!/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. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class WalletLocktimeTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + + mtp_tip = node.getblockheader(node.getbestblockhash())["mediantime"] + + self.log.info("Get new address with label") + label = "timelock⌛🔓" + address = node.getnewaddress(label=label) + + self.log.info("Send to new address with locktime") + node.send( + outputs={address: 5}, + options={"locktime": mtp_tip - 1}, + ) + self.generate(node, 1) + + self.log.info("Check that clock can not change finality of confirmed txs") + amount_before_ad = node.getreceivedbyaddress(address) + amount_before_lb = node.getreceivedbylabel(label) + list_before_ad = node.listreceivedbyaddress(address_filter=address) + list_before_lb = node.listreceivedbylabel(include_empty=False) + balance_before = node.getbalances()["mine"]["trusted"] + coin_before = node.listunspent(maxconf=1) + node.setmocktime(mtp_tip - 1) + assert_equal(node.getreceivedbyaddress(address), amount_before_ad) + assert_equal(node.getreceivedbylabel(label), amount_before_lb) + assert_equal(node.listreceivedbyaddress(address_filter=address), list_before_ad) + assert_equal(node.listreceivedbylabel(include_empty=False), list_before_lb) + assert_equal(node.getbalances()["mine"]["trusted"], balance_before) + assert_equal(node.listunspent(maxconf=1), coin_before) + + +if __name__ == "__main__": + WalletLocktimeTest().main() |