wallet_send.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2020-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test the send RPC command."""
6
7 from decimal import Decimal, getcontext
8 from itertools import product
9
10 from test_framework.descriptors import descsum_create
11 from test_framework.extendedkey import ExtendedPrivateKey
12 from test_framework.test_framework import BitcoinTestFramework
13 from test_framework.util import (
14 assert_not_equal,
15 assert_equal,
16 assert_fee_amount,
17 assert_greater_than,
18 assert_greater_than_or_equal,
19 assert_raises_rpc_error,
20 count_bytes,
21 JSONRPCException,
22 )
23 from test_framework.wallet_util import (
24 calculate_input_weight,
25 generate_keypair,
26 )
27
28
29 class WalletSendTest(BitcoinTestFramework):
30 def set_test_params(self):
31 self.num_nodes = 2
32 # whitelist peers to speed up tx relay / mempool sync
33 self.noban_tx_relay = True
34 self.supports_cli = False
35 self.extra_args = [["-datacarriersize=16"]] * self.num_nodes
36 getcontext().prec = 8 # Satoshi precision for Decimal
37
38 def skip_test_if_missing_module(self):
39 self.skip_if_no_wallet()
40
41 def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
42 arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
43 conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
44 inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None, change_type=None,
45 locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
46 expect_error=None, solving_data=None, minconf=None, nonmempool=False):
47 assert_not_equal((amount is None), (data is None))
48
49 from_balance_before = from_wallet.getbalances()["mine"]["trusted"]
50 if include_unsafe:
51 from_balance_before += from_wallet.getbalances()["mine"]["untrusted_pending"]
52
53 if to_wallet is None:
54 assert amount is None
55 else:
56 to_untrusted_pending_before = to_wallet.getbalances()["mine"]["untrusted_pending"]
57
58 if amount:
59 dest = to_wallet.getnewaddress()
60 outputs = {dest: amount}
61 else:
62 outputs = {"data": data}
63
64 # Construct options dictionary
65 options = {}
66 if add_to_wallet is not None:
67 options["add_to_wallet"] = add_to_wallet
68 else:
69 if psbt:
70 add_to_wallet = False
71 else:
72 add_to_wallet = from_wallet.getwalletinfo()["private_keys_enabled"] # Default value
73 if psbt is not None:
74 options["psbt"] = psbt
75 if conf_target is not None:
76 options["conf_target"] = conf_target
77 if estimate_mode is not None:
78 options["estimate_mode"] = estimate_mode
79 if fee_rate is not None:
80 options["fee_rate"] = fee_rate
81 if inputs is not None:
82 options["inputs"] = inputs
83 if add_inputs is not None:
84 options["add_inputs"] = add_inputs
85 if include_unsafe is not None:
86 options["include_unsafe"] = include_unsafe
87 if change_address is not None:
88 options["change_address"] = change_address
89 if change_position is not None:
90 options["change_position"] = change_position
91 if change_type is not None:
92 options["change_type"] = change_type
93 if locktime is not None:
94 options["locktime"] = locktime
95 if lock_unspents is not None:
96 options["lock_unspents"] = lock_unspents
97 if replaceable is None:
98 replaceable = True # default
99 else:
100 options["replaceable"] = replaceable
101 if subtract_fee_from_outputs is not None:
102 options["subtract_fee_from_outputs"] = subtract_fee_from_outputs
103 if solving_data is not None:
104 options["solving_data"] = solving_data
105 if minconf is not None:
106 options["minconf"] = minconf
107
108 if len(options.keys()) == 0:
109 options = None
110
111 expect_sign = from_wallet.getwalletinfo()["private_keys_enabled"]
112 expect_sign = expect_sign and solving_data is None
113 if inputs is not None:
114 expect_sign = expect_sign and all(["weight" not in i for i in inputs])
115
116 if expect_error is None:
117 res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
118 else:
119 try:
120 assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
121 outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
122 except AssertionError:
123 # Provide debug info if the test fails
124 self.log.error("Unexpected successful result:")
125 self.log.error(arg_conf_target)
126 self.log.error(arg_estimate_mode)
127 self.log.error(arg_fee_rate)
128 self.log.error(options)
129 res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
130 self.log.error(res)
131 if "txid" in res and add_to_wallet:
132 self.log.error("Transaction details:")
133 try:
134 tx = from_wallet.gettransaction(res["txid"])
135 self.log.error(tx)
136 self.log.error("testmempoolaccept (transaction may already be in mempool):")
137 self.log.error(from_wallet.testmempoolaccept([tx["hex"]]))
138 except JSONRPCException as exc:
139 self.log.error(exc)
140
141 raise
142
143 return
144
145 if locktime:
146 assert_equal(from_wallet.gettransaction(txid=res["txid"], verbose=True)["decoded"]["locktime"], locktime)
147 return res
148 else:
149 if add_to_wallet:
150 decoded_tx = from_wallet.gettransaction(txid=res["txid"], verbose=True)["decoded"]
151 # the locktime should be within 100 blocks of the
152 # block height
153 assert_greater_than_or_equal(decoded_tx["locktime"], from_wallet.getblockcount() - 100)
154
155 if expect_sign:
156 assert_equal(res["complete"], True)
157 assert "txid" in res
158 else:
159 assert_equal(res["complete"], False)
160 assert "txid" not in res
161 assert "psbt" in res
162
163 from_balance = from_wallet.getbalances()["mine"]["trusted"]
164 if include_unsafe:
165 from_balance += from_wallet.getbalances()["mine"]["untrusted_pending"]
166
167 if add_to_wallet:
168 # Ensure transaction exists in the wallet:
169 tx = from_wallet.gettransaction(res["txid"])
170 assert tx
171 if nonmempool:
172 assert_raises_rpc_error(-5, "No such mempool transaction", from_wallet.getrawtransaction, res["txid"])
173 assert from_wallet.getbalances()["mine"]["nonmempool"] < 0
174 else:
175 # Ensure transaction exists in the mempool:
176 tx = from_wallet.getrawtransaction(res["txid"], True)
177 assert tx
178 if amount:
179 if subtract_fee_from_outputs:
180 assert_equal(from_balance_before - from_balance, amount)
181 else:
182 assert_greater_than(from_balance_before - from_balance, amount)
183 else:
184 assert next((out for out in tx["vout"] if out["scriptPubKey"]["asm"] == "OP_RETURN 35"), None)
185 else:
186 assert_equal(from_balance_before, from_balance)
187
188 if to_wallet:
189 self.sync_mempools()
190 if add_to_wallet:
191 if not subtract_fee_from_outputs:
192 assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before + Decimal(amount if amount else 0))
193 else:
194 assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before)
195
196 return res
197
198 def run_test(self):
199 self.log.info("Setup wallets...")
200 # w0 is a wallet with coinbase rewards
201 w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
202 # w1 is a regular wallet
203 self.nodes[1].createwallet(wallet_name="w1")
204 w1 = self.nodes[1].get_wallet_rpc("w1")
205 # w2 contains the private keys for w3
206 self.nodes[1].createwallet(wallet_name="w2", blank=True)
207 w2 = self.nodes[1].get_wallet_rpc("w2")
208 extendedkey = ExtendedPrivateKey.generate()
209 xpriv = extendedkey.to_string()
210 xpub = extendedkey.pubkey().to_string()
211 w2.importdescriptors([{
212 "desc": descsum_create("wpkh(" + xpriv + "/0/0/*)"),
213 "timestamp": "now",
214 "range": [0, 100],
215 "active": True
216 },{
217 "desc": descsum_create("wpkh(" + xpriv + "/0/1/*)"),
218 "timestamp": "now",
219 "range": [0, 100],
220 "active": True,
221 "internal": True
222 }])
223
224 # w3 is a watch-only wallet, based on w2
225 self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
226 w3 = self.nodes[1].get_wallet_rpc("w3")
227 # Match the privkeys in w2 for descriptors
228 res = w3.importdescriptors([{
229 "desc": descsum_create("wpkh(" + xpub + "/0/0/*)"),
230 "timestamp": "now",
231 "range": [0, 100],
232 "active": True,
233 },{
234 "desc": descsum_create("wpkh(" + xpub + "/0/1/*)"),
235 "timestamp": "now",
236 "range": [0, 100],
237 "active": True,
238 "internal": True,
239 }])
240 assert_equal(res, [{"success": True}, {"success": True}])
241
242 for _ in range(3):
243 a2_receive = w2.getnewaddress()
244 w0.sendtoaddress(a2_receive, 10) # fund w3
245 self.generate(self.nodes[0], 1)
246
247 self.log.info("Send to address...")
248 self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
249 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True)
250
251 self.log.info("Don't broadcast...")
252 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
253 assert res["hex"]
254
255 self.log.info("Return PSBT...")
256 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, psbt=True)
257 assert res["psbt"]
258
259 self.log.info("Create transaction that spends to address, but don't broadcast...")
260 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
261 # conf_target & estimate_mode can be set as argument or option
262 res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical", add_to_wallet=False)
263 res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=1, estimate_mode="economical", add_to_wallet=False)
264 assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
265 self.nodes[1].decodepsbt(res2["psbt"])["fee"])
266 # but not at the same time
267 for mode in ["unset", "economical", "conservative"]:
268 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
269 conf_target=1, estimate_mode=mode, add_to_wallet=False,
270 expect_error=(-8, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"))
271
272 self.log.info("Create PSBT from watch-only wallet w3, sign with w2...")
273 res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
274 res = w2.walletprocesspsbt(res["psbt"])
275 assert res["complete"]
276
277 self.log.info("Create OP_RETURN...")
278 self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
279 self.test_send(from_wallet=w0, data="Hello World", expect_error=(-8, "Data must be hexadecimal string (not 'Hello World')"))
280 self.test_send(from_wallet=w0, data="23")
281 res = self.test_send(from_wallet=w3, data="23")
282 res = w2.walletprocesspsbt(res["psbt"])
283 assert res["complete"]
284
285 self.log.info("Create mempool-invalid tx (due to large OP_RETURN)...")
286 self.test_send(from_wallet=w0, data=b"The quick brown fox jumps over the lazy dog".hex(), nonmempool=True)
287
288 self.log.info("Test setting explicit fee rate")
289 res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate="1", add_to_wallet=False)
290 res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate="1", add_to_wallet=False)
291 assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"], self.nodes[1].decodepsbt(res2["psbt"])["fee"])
292
293 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=7, add_to_wallet=False)
294 fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
295 assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00007"))
296
297 # "unset" and None are treated the same for estimate_mode
298 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, estimate_mode="unset", add_to_wallet=False)
299 fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
300 assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00002"))
301
302 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=4.531, add_to_wallet=False)
303 fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
304 assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00004531"))
305
306 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, add_to_wallet=False)
307 fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
308 assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00003"))
309
310 # Test that passing fee_rate as both an argument and an option raises.
311 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
312 expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
313
314 assert_raises_rpc_error(-8, "Use fee_rate (sat/vB) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
315
316 assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
317
318 for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
319 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
320 expect_error=(-8, "Invalid conf_target, must be between 1 and 1008")) # max value of 1008 per src/policy/fees/block_policy_estimator.h
321 msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
322 for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
323 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode, expect_error=(-8, msg))
324 for mode in ["", "foo", Decimal("3.141592")]:
325 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode, expect_error=(-8, msg))
326 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode, expect_error=(-8, msg))
327 assert_raises_rpc_error(-8, msg, w0.send, {w1.getnewaddress(): 1}, 0.1, mode)
328
329 for mode in ["economical", "conservative"]:
330 for k, v in {"string": "true", "bool": True, "object": {"foo": "bar"}}.items():
331 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
332 expect_error=(-3, f"JSON value of type {k} for field conf_target is not of expected type number"))
333
334 # Test setting explicit fee rate just below the minimum of 1 sat/vB.
335 self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
336 msg = "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
337 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.999, expect_error=(-4, msg))
338 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0.999, expect_error=(-4, msg))
339
340 self.log.info("Explicit fee rate raises if invalid fee_rate is passed")
341 # Test fee_rate with zero values.
342 msg = "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
343 for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
344 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=zero_value, expect_error=(-4, msg))
345 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=zero_value, expect_error=(-4, msg))
346 msg = "Invalid amount"
347 # Test fee_rate values that don't pass fixed-point parsing checks.
348 for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
349 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
350 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
351 # Test fee_rate values that cannot be represented in sat/vB.
352 for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]:
353 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
354 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
355 # Test fee_rate out of range (negative number).
356 msg = "Amount out of range"
357 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=-1, expect_error=(-3, msg))
358 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=-1, expect_error=(-3, msg))
359 # Test type error.
360 msg = "Amount is not a number or string"
361 for invalid_value in [True, {"foo": "bar"}]:
362 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
363 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
364
365 # TODO: Return hex if fee rate is below -maxmempool
366 # res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)
367 # assert res["hex"]
368 # hex = res["hex"]
369 # res = self.nodes[0].testmempoolaccept([hex])
370 # assert not res[0]["allowed"]
371 # assert_equal(res[0]["reject-reason"], "...") # low fee
372 # assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.000001"))
373
374 self.log.info("If inputs are specified, do not automatically add more...")
375 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[], add_to_wallet=False)
376 assert res["complete"]
377 utxo1 = w0.listunspent()[0]
378 assert_equal(utxo1["amount"], 50)
379 ERR_NOT_ENOUGH_PRESET_INPUTS = "The preselected coins total amount does not cover the transaction target. " \
380 "Please allow other inputs to be automatically selected or include more coins manually"
381 self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1],
382 expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
383 self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=False,
384 expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
385 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=True, add_to_wallet=False)
386 assert res["complete"]
387
388 self.log.info("Manual change address and position...")
389 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, change_address="not an address",
390 expect_error=(-5, "Change address must be a valid bitcoin address"))
391 change_address = w0.getnewaddress()
392 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address)
393 assert res["complete"]
394 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0)
395 assert res["complete"]
396 assert_equal(self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"], change_address)
397 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0)
398 assert res["complete"]
399 change_address = self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"]
400 assert change_address[0] in ("m", "n")
401
402 self.log.info("Set lock time...")
403 height = self.nodes[0].getblockchaininfo()["blocks"]
404 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, locktime=height + 1)
405 assert res["complete"]
406 assert res["txid"]
407 txid = res["txid"]
408 # Although the wallet finishes the transaction, it can't be added to the mempool yet:
409 hex = self.nodes[0].gettransaction(res["txid"])["hex"]
410 res = self.nodes[0].testmempoolaccept([hex])
411 assert not res[0]["allowed"]
412 assert_equal(res[0]["reject-reason"], "non-final")
413 # It shouldn't be confirmed in the next block
414 self.generate(self.nodes[0], 1)
415 assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 0)
416 # The mempool should allow it now:
417 res = self.nodes[0].testmempoolaccept([hex])
418 assert res[0]["allowed"]
419 # Don't wait for wallet to add it to the mempool:
420 res = self.nodes[0].sendrawtransaction(hex)
421 self.generate(self.nodes[0], 1)
422 assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 1)
423
424 self.log.info("Lock unspents...")
425 utxo1 = w0.listunspent()[0]
426 assert_greater_than(utxo1["amount"], 1)
427 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False, lock_unspents=True)
428 assert res["complete"]
429 locked_coins = w0.listlockunspent()
430 assert_equal(len(locked_coins), 1)
431 # Locked coins are automatically unlocked when manually selected
432 res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False)
433 assert res["complete"]
434
435 self.log.info("Subtract fee from output")
436 self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
437
438 self.log.info("Include unsafe inputs")
439 self.nodes[1].createwallet(wallet_name="w5")
440 w5 = self.nodes[1].get_wallet_rpc("w5")
441 self.test_send(from_wallet=w0, to_wallet=w5, amount=2)
442 self.test_send(from_wallet=w5, to_wallet=w0, amount=1, expect_error=(-4, "Insufficient funds"))
443 res = self.test_send(from_wallet=w5, to_wallet=w0, amount=1, include_unsafe=True)
444 assert res["complete"]
445
446 self.log.info("Minconf")
447 self.nodes[1].createwallet(wallet_name="minconfw")
448 minconfw= self.nodes[1].get_wallet_rpc("minconfw")
449 self.test_send(from_wallet=w0, to_wallet=minconfw, amount=2)
450 self.generate(self.nodes[0], 3)
451 self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=4, expect_error=(-4, "Insufficient funds"))
452 self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=-4, expect_error=(-8, "Negative minconf"))
453 res = self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=3)
454 assert res["complete"]
455
456 self.log.info("External outputs")
457 privkey, _ = generate_keypair(wif=True)
458
459 self.nodes[1].createwallet("extsend")
460 ext_wallet = self.nodes[1].get_wallet_rpc("extsend")
461 self.nodes[1].createwallet("extfund")
462 ext_fund = self.nodes[1].get_wallet_rpc("extfund")
463
464 # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this
465 desc = descsum_create("sh(wsh(pkh({})))".format(privkey))
466 res = ext_fund.importdescriptors([{"desc": desc, "timestamp": "now"}])
467 assert res[0]["success"]
468 addr = self.nodes[0].deriveaddresses(desc)[0]
469 addr_info = ext_fund.getaddressinfo(addr)
470
471 self.nodes[0].sendtoaddress(addr, 10)
472 self.nodes[0].sendtoaddress(ext_wallet.getnewaddress(), 10)
473 self.generate(self.nodes[0], 6)
474 ext_utxo = ext_fund.listunspent(addresses=[addr])[0]
475
476 # An external input without solving data should result in an error
477 self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, expect_error=(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"])))
478
479 # But funding should work when the solving data is provided
480 res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]})
481 signed = ext_wallet.walletprocesspsbt(res["psbt"])
482 signed = ext_fund.walletprocesspsbt(res["psbt"])
483 assert signed["complete"]
484
485 res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, solving_data={"descriptors": [desc]})
486 signed = ext_wallet.walletprocesspsbt(res["psbt"])
487 signed = ext_fund.walletprocesspsbt(res["psbt"])
488 assert signed["complete"]
489
490 dec = self.nodes[0].decodepsbt(signed["psbt"])
491 for psbt_in in dec["inputs"]:
492 if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:
493 break
494 scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
495 witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
496 input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
497
498 # Input weight error conditions
499 assert_raises_rpc_error(
500 -8,
501 "Input weights should be specified in inputs rather than in options.",
502 ext_wallet.send,
503 outputs={self.nodes[0].getnewaddress(): 15},
504 options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]}
505 )
506
507 target_fee_rate_sat_vb = 10
508 # Funding should also work when input weights are provided
509 res = self.test_send(
510 from_wallet=ext_wallet,
511 to_wallet=self.nodes[0],
512 amount=15,
513 inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}],
514 add_inputs=True,
515 psbt=True,
516 fee_rate=target_fee_rate_sat_vb
517 )
518 signed = ext_wallet.walletprocesspsbt(res["psbt"])
519 signed = ext_fund.walletprocesspsbt(res["psbt"])
520 assert signed["complete"]
521 testres = self.nodes[0].testmempoolaccept([signed["hex"]])[0]
522 assert_equal(testres["allowed"], True)
523 actual_fee_rate_sat_vb = Decimal(testres["fees"]["base"]) * Decimal(1e8) / Decimal(testres["vsize"])
524 # Due to ECDSA signatures not always being the same length, the actual fee rate may be slightly different
525 # but rounded to nearest integer, it should be the same as the target fee rate
526 assert_equal(round(actual_fee_rate_sat_vb), target_fee_rate_sat_vb)
527
528 # Check tx creation size limits
529 self.test_weight_limits()
530
531 def test_weight_limits(self):
532 self.log.info("Test weight limits")
533
534 self.nodes[1].createwallet("test_weight_limits")
535 wallet = self.nodes[1].get_wallet_rpc("test_weight_limits")
536
537 # Generate future inputs; 272 WU per input (273 when high-s).
538 # Picking 1471 inputs will exceed the max standard tx weight.
539 outputs = []
540 for _ in range(1472):
541 outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
542 self.nodes[0].send(outputs=outputs)
543 self.generate(self.nodes[0], 1)
544
545 # 1) Try to fund transaction only using the preset inputs
546 inputs = wallet.listunspent()
547 assert_raises_rpc_error(-4, "Transaction too large",
548 wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False})
549
550 # 2) Let the wallet fund the transaction
551 assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
552 wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}])
553
554 # 3) Pre-select some inputs and let the wallet fill-up the remaining amount
555 inputs = inputs[0:1000]
556 assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
557 wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True})
558
559 self.nodes[1].unloadwallet("test_weight_limits")
560
561
562 if __name__ == '__main__':
563 WalletSendTest(__file__).main()
564