wallet_send.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-2022 The Limenka 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.authproxy import JSONRPCException
  11  from test_framework.descriptors import descsum_create
  12  from test_framework.test_framework import LimenkaTestFramework
  13  from test_framework.util import (
  14      assert_equal,
  15      assert_fee_amount,
  16      assert_greater_than,
  17      assert_greater_than_or_equal,
  18      assert_raises_rpc_error,
  19      count_bytes,
  20  )
  21  from test_framework.wallet_util import (
  22      calculate_input_weight,
  23      generate_keypair,
  24  )
  25  
  26  
  27  class WalletSendTest(LimenkaTestFramework):
  28      def add_options(self, parser):
  29          self.add_wallet_options(parser)
  30  
  31      def set_test_params(self):
  32          self.num_nodes = 2
  33          # whitelist peers to speed up tx relay / mempool sync
  34          self.noban_tx_relay = True
  35          self.extra_args = [
  36              ["-walletrbf=1"],
  37              ["-walletrbf=1"]
  38          ]
  39          getcontext().prec = 8 # Satoshi precision for Decimal
  40  
  41      def skip_test_if_missing_module(self):
  42          self.skip_if_no_wallet()
  43  
  44      def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
  45                    arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
  46                    conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
  47                    inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None, change_type=None,
  48                    include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
  49                    expect_error=None, solving_data=None, minconf=None):
  50          assert (amount is None) != (data is None)
  51  
  52          from_balance_before = from_wallet.getbalances()["mine"]["trusted"]
  53          if include_unsafe:
  54              from_balance_before += from_wallet.getbalances()["mine"]["untrusted_pending"]
  55  
  56          if to_wallet is None:
  57              assert amount is None
  58          else:
  59              to_untrusted_pending_before = to_wallet.getbalances()["mine"]["untrusted_pending"]
  60  
  61          if amount:
  62              dest = to_wallet.getnewaddress()
  63              outputs = {dest: amount}
  64          else:
  65              outputs = {"data": data}
  66  
  67          # Construct options dictionary
  68          options = {}
  69          if add_to_wallet is not None:
  70              options["add_to_wallet"] = add_to_wallet
  71          else:
  72              if psbt:
  73                  add_to_wallet = False
  74              else:
  75                  add_to_wallet = from_wallet.getwalletinfo()["private_keys_enabled"] # Default value
  76          if psbt is not None:
  77              options["psbt"] = psbt
  78          if conf_target is not None:
  79              options["conf_target"] = conf_target
  80          if estimate_mode is not None:
  81              options["estimate_mode"] = estimate_mode
  82          if fee_rate is not None:
  83              options["fee_rate"] = fee_rate
  84          if inputs is not None:
  85              options["inputs"] = inputs
  86          if add_inputs is not None:
  87              options["add_inputs"] = add_inputs
  88          if include_unsafe is not None:
  89              options["include_unsafe"] = include_unsafe
  90          if change_address is not None:
  91              options["change_address"] = change_address
  92          if change_position is not None:
  93              options["change_position"] = change_position
  94          if change_type is not None:
  95              options["change_type"] = change_type
  96          if include_watching is not None:
  97              options["include_watching"] = include_watching
  98          if locktime is not None:
  99              options["locktime"] = locktime
 100          if lock_unspents is not None:
 101              options["lock_unspents"] = lock_unspents
 102          if replaceable is None:
 103              replaceable = True # default
 104          else:
 105              options["replaceable"] = replaceable
 106          if subtract_fee_from_outputs is not None:
 107              options["subtract_fee_from_outputs"] = subtract_fee_from_outputs
 108          if solving_data is not None:
 109              options["solving_data"] = solving_data
 110          if minconf is not None:
 111              options["minconf"] = minconf
 112  
 113          if len(options.keys()) == 0:
 114              options = None
 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 from_wallet.getwalletinfo()["private_keys_enabled"] and not include_watching:
 156              assert_equal(res["complete"], True)
 157              assert "txid" in res
 158          else:
 159              assert_equal(res["complete"], False)
 160              assert not "txid" 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 and not include_watching:
 168              # Ensure transaction exists in the wallet:
 169              tx = from_wallet.gettransaction(res["txid"])
 170              assert tx
 171              assert_equal(tx["bip125-replaceable"], "yes" if replaceable else "no")
 172              # Ensure transaction exists in the mempool:
 173              tx = from_wallet.getrawtransaction(res["txid"], True)
 174              assert tx
 175              if amount:
 176                  if subtract_fee_from_outputs:
 177                      assert_equal(from_balance_before - from_balance, amount)
 178                  else:
 179                      assert_greater_than(from_balance_before - from_balance, amount)
 180              else:
 181                  assert next((out for out in tx["vout"] if out["scriptPubKey"]["asm"] == "OP_RETURN 35"), None)
 182          else:
 183              assert_equal(from_balance_before, from_balance)
 184  
 185          if to_wallet:
 186              self.sync_mempools()
 187              if add_to_wallet:
 188                  if not subtract_fee_from_outputs:
 189                      assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before + Decimal(amount if amount else 0))
 190              else:
 191                  assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before)
 192  
 193          return res
 194  
 195      def run_test(self):
 196          self.log.info("Setup wallets...")
 197          # w0 is a wallet with coinbase rewards
 198          w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
 199          # w1 is a regular wallet
 200          self.nodes[1].createwallet(wallet_name="w1")
 201          w1 = self.nodes[1].get_wallet_rpc("w1")
 202          # w2 contains the private keys for w3
 203          self.nodes[1].createwallet(wallet_name="w2", blank=True)
 204          w2 = self.nodes[1].get_wallet_rpc("w2")
 205          xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
 206          xpub = "tpubD6NzVbkrYhZ4YkEfMbRJkQyZe7wTkbTNRECozCtJPtdLRn6cT1QKb8yHjwAPcAr26eHBFYs5iLiFFnCbwPRsncCKUKCfubHDMGKzMVcN1Jg"
 207          if self.options.descriptors:
 208              w2.importdescriptors([{
 209                  "desc": descsum_create("wpkh(" + xpriv + "/0/0/*)"),
 210                  "timestamp": "now",
 211                  "range": [0, 100],
 212                  "active": True
 213              },{
 214                  "desc": descsum_create("wpkh(" + xpriv + "/0/1/*)"),
 215                  "timestamp": "now",
 216                  "range": [0, 100],
 217                  "active": True,
 218                  "internal": True
 219              }])
 220          else:
 221              w2.sethdseed(True)
 222  
 223          # w3 is a watch-only wallet, based on w2
 224          self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
 225          w3 = self.nodes[1].get_wallet_rpc("w3")
 226          if self.options.descriptors:
 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                  "keypool": True,
 233                  "active": True,
 234                  "watchonly": True
 235              },{
 236                  "desc": descsum_create("wpkh(" + xpub + "/0/1/*)"),
 237                  "timestamp": "now",
 238                  "range": [0, 100],
 239                  "keypool": True,
 240                  "active": True,
 241                  "internal": True,
 242                  "watchonly": True
 243              }])
 244              assert_equal(res, [{"success": True}, {"success": True}])
 245  
 246          for _ in range(3):
 247              a2_receive = w2.getnewaddress()
 248              if not self.options.descriptors:
 249                  # Because legacy wallets use exclusively hardened derivation, we can't do a ranged import like we do for descriptors
 250                  a2_change = w2.getrawchangeaddress() # doesn't actually use change derivation
 251                  res = w3.importmulti([{
 252                      "desc": w2.getaddressinfo(a2_receive)["desc"],
 253                      "timestamp": "now",
 254                      "keypool": True,
 255                      "watchonly": True
 256                  },{
 257                      "desc": w2.getaddressinfo(a2_change)["desc"],
 258                      "timestamp": "now",
 259                      "keypool": True,
 260                      "internal": True,
 261                      "watchonly": True
 262                  }])
 263                  assert_equal(res, [{"success": True}, {"success": True}])
 264  
 265          w0.sendtoaddress(a2_receive, 10) # fund w3
 266          self.generate(self.nodes[0], 1)
 267  
 268          if not self.options.descriptors:
 269              # w4 has private keys enabled, but only contains watch-only keys (from w2)
 270              # This is legacy wallet behavior only as descriptor wallets don't allow watchonly and non-watchonly things in the same wallet.
 271              self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
 272              w4 = self.nodes[1].get_wallet_rpc("w4")
 273              for _ in range(3):
 274                  a2_receive = w2.getnewaddress()
 275                  res = w4.importmulti([{
 276                      "desc": w2.getaddressinfo(a2_receive)["desc"],
 277                      "timestamp": "now",
 278                      "keypool": False,
 279                      "watchonly": True
 280                  }])
 281                  assert_equal(res, [{"success": True}])
 282  
 283              w0.sendtoaddress(a2_receive, 10) # fund w4
 284              self.generate(self.nodes[0], 1)
 285  
 286          self.log.info("Send to address...")
 287          self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
 288          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True)
 289  
 290          self.log.info("Don't broadcast...")
 291          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
 292          assert res["hex"]
 293  
 294          self.log.info("Return PSBT...")
 295          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, psbt=True)
 296          assert res["psbt"]
 297  
 298          self.log.info("Create transaction that spends to address, but don't broadcast...")
 299          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False)
 300          # conf_target & estimate_mode can be set as argument or option
 301          res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical", add_to_wallet=False)
 302          res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=1, estimate_mode="economical", add_to_wallet=False)
 303          assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
 304                       self.nodes[1].decodepsbt(res2["psbt"])["fee"])
 305          # but not at the same time
 306          for mode in ["unset", "economical", "conservative"]:
 307              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
 308                  conf_target=1, estimate_mode=mode, add_to_wallet=False,
 309                  expect_error=(-8, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"))
 310  
 311          self.log.info("Create PSBT from watch-only wallet w3, sign with w2...")
 312          res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
 313          res = w2.walletprocesspsbt(res["psbt"])
 314          assert res["complete"]
 315  
 316          # verify that fee estimation modes parse case insensitively
 317          self.log.info("Testing case insensitive fee estimation mode parse")
 318          for mode in ["ecoNOMICAL", "economical", "ECONOMICAL"]:
 319              res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 320                  estimate_mode=mode, conf_target=1, add_to_wallet=False
 321              )
 322              assert_equal(res["complete"], True)
 323  
 324              res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 325                  arg_estimate_mode=mode, arg_conf_target=1, add_to_wallet=False
 326              )
 327              assert_equal(res["complete"], True)
 328  
 329          # Verify that different variations of 'unset' still counts as
 330          # not setting the estimation mode
 331          for mode in ["unSET", "unset", "UNSET"]:
 332              self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 333                  arg_estimate_mode=mode, arg_conf_target=1, add_to_wallet=False,  expect_error = (-8, 'Specify estimate_mode')
 334              )
 335  
 336              self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 337                  estimate_mode=mode, conf_target=1, add_to_wallet=False,  expect_error = (-8, 'Specify estimate_mode')
 338              )
 339  
 340          # Verify that 'estimate_mode' requires a confirmation target
 341          for mode in ["ecoNOMICAL", "economical", "ECONOMICAL"]:
 342              self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 343                  estimate_mode=mode, conf_target=None, add_to_wallet=False,  expect_error = (-8, 'estimate_mode should be passed with conf_target')
 344              )
 345  
 346              self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
 347                  arg_estimate_mode=mode, arg_conf_target=None, add_to_wallet=False,  expect_error = (-8, 'estimate_mode should be passed with conf_target')
 348              )
 349  
 350          if not self.options.descriptors:
 351              # Descriptor wallets do not allow mixed watch-only and non-watch-only things in the same wallet.
 352              # This is specifically testing that w4 ignores its own private keys and creates a psbt with send
 353              # which is not something that needs to be tested in descriptor wallets.
 354              self.log.info("Create PSBT from wallet w4 with watch-only keys, sign with w2...")
 355              self.test_send(from_wallet=w4, to_wallet=w1, amount=1, expect_error=(-4, "Insufficient funds"))
 356              res = self.test_send(from_wallet=w4, to_wallet=w1, amount=1, include_watching=True, add_to_wallet=False)
 357              res = w2.walletprocesspsbt(res["psbt"])
 358              assert res["complete"]
 359  
 360          self.log.info("Create OP_RETURN...")
 361          self.test_send(from_wallet=w0, to_wallet=w1, amount=1)
 362          self.test_send(from_wallet=w0, data="Hello World", expect_error=(-8, "Data must be hexadecimal string (not 'Hello World')"))
 363          self.test_send(from_wallet=w0, data="23")
 364          res = self.test_send(from_wallet=w3, data="23")
 365          res = w2.walletprocesspsbt(res["psbt"])
 366          assert res["complete"]
 367  
 368          self.log.info("Test setting explicit fee rate")
 369          res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate="1", add_to_wallet=False)
 370          res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate="1", add_to_wallet=False)
 371          assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"], self.nodes[1].decodepsbt(res2["psbt"])["fee"])
 372  
 373          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=7, add_to_wallet=False)
 374          fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
 375          assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00007"))
 376  
 377          # "unset" and None are treated the same for estimate_mode
 378          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, estimate_mode="unset", add_to_wallet=False)
 379          fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
 380          assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00002"))
 381  
 382          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=4.531, add_to_wallet=False)
 383          fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
 384          assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00004531"))
 385  
 386          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, add_to_wallet=False)
 387          fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
 388          assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00003"))
 389  
 390          # Test that passing fee_rate as both an argument and an option raises.
 391          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
 392                         expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
 393  
 394          assert_raises_rpc_error(-8, "Use fee_rate (sat/vB) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
 395  
 396          assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
 397  
 398          for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
 399              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
 400                  expect_error=(-8, "Invalid conf_target, must be between 1 and 1008"))  # max value of 1008 per src/policy/fees.h
 401          msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
 402          for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
 403              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode, expect_error=(-8, msg))
 404          for mode in ["", "foo", Decimal("3.141592")]:
 405              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode, expect_error=(-8, msg))
 406              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode, expect_error=(-8, msg))
 407              assert_raises_rpc_error(-8, msg, w0.send, {w1.getnewaddress(): 1}, 0.1, mode)
 408  
 409          for mode in ["economical", "conservative"]:
 410              for k, v in {"string": "true", "bool": True, "object": {"foo": "bar"}}.items():
 411                  self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
 412                      expect_error=(-3, f"JSON value of type {k} for field conf_target is not of expected type number"))
 413  
 414          # Test setting explicit fee rate just below the minimum of 1 sat/vB.
 415          self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
 416          msg = "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
 417          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.999, expect_error=(-4, msg))
 418          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0.999, expect_error=(-4, msg))
 419  
 420          self.log.info("Explicit fee rate raises if invalid fee_rate is passed")
 421          # Test fee_rate with zero values.
 422          msg = "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
 423          for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
 424              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=zero_value, expect_error=(-4, msg))
 425              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=zero_value, expect_error=(-4, msg))
 426          msg = "Invalid amount"
 427          # Test fee_rate values that don't pass fixed-point parsing checks.
 428          for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
 429              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
 430              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
 431          # Test fee_rate values that cannot be represented in sat/vB.
 432          for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]:
 433              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
 434              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
 435          # Test fee_rate out of range (negative number).
 436          msg = "Amount out of range"
 437          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=-1, expect_error=(-3, msg))
 438          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=-1, expect_error=(-3, msg))
 439          # Test type error.
 440          msg = "Amount is not a number or string"
 441          for invalid_value in [True, {"foo": "bar"}]:
 442              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
 443              self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
 444  
 445          # TODO: Return hex if fee rate is below -maxmempool
 446          # res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)
 447          # assert res["hex"]
 448          # hex = res["hex"]
 449          # res = self.nodes[0].testmempoolaccept([hex])
 450          # assert not res[0]["allowed"]
 451          # assert_equal(res[0]["reject-reason"], "...") # low fee
 452          # assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.000001"))
 453  
 454          self.log.info("If inputs are specified, do not automatically add more...")
 455          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[], add_to_wallet=False)
 456          assert res["complete"]
 457          utxo1 = w0.listunspent()[0]
 458          assert_equal(utxo1["amount"], 50)
 459          ERR_NOT_ENOUGH_PRESET_INPUTS = "The preselected coins total amount does not cover the transaction target. " \
 460                                         "Please allow other inputs to be automatically selected or include more coins manually"
 461          self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1],
 462                         expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
 463          self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=False,
 464                         expect_error=(-4, ERR_NOT_ENOUGH_PRESET_INPUTS))
 465          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=51, inputs=[utxo1], add_inputs=True, add_to_wallet=False)
 466          assert res["complete"]
 467  
 468          self.log.info("Manual change address and position...")
 469          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, change_address="not an address",
 470                         expect_error=(-5, "Change address must be a valid limenka address"))
 471          change_address = w0.getnewaddress()
 472          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address)
 473          assert res["complete"]
 474          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0)
 475          assert res["complete"]
 476          assert_equal(self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"], change_address)
 477          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0)
 478          assert res["complete"]
 479          change_address = self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"]
 480          assert change_address[0] == "m" or change_address[0] == "n"
 481  
 482          self.log.info("Set lock time...")
 483          height = self.nodes[0].getblockchaininfo()["blocks"]
 484          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, locktime=height + 1)
 485          assert res["complete"]
 486          assert res["txid"]
 487          txid = res["txid"]
 488          # Although the wallet finishes the transaction, it can't be added to the mempool yet:
 489          hex = self.nodes[0].gettransaction(res["txid"])["hex"]
 490          res = self.nodes[0].testmempoolaccept([hex])
 491          assert not res[0]["allowed"]
 492          assert_equal(res[0]["reject-reason"], "non-final")
 493          # It shouldn't be confirmed in the next block
 494          self.generate(self.nodes[0], 1)
 495          assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 0)
 496          # The mempool should allow it now:
 497          res = self.nodes[0].testmempoolaccept([hex])
 498          assert res[0]["allowed"]
 499          # Don't wait for wallet to add it to the mempool:
 500          res = self.nodes[0].sendrawtransaction(hex)
 501          self.generate(self.nodes[0], 1)
 502          assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 1)
 503  
 504          self.log.info("Lock unspents...")
 505          utxo1 = w0.listunspent()[0]
 506          assert_greater_than(utxo1["amount"], 1)
 507          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False, lock_unspents=True)
 508          assert res["complete"]
 509          locked_coins = w0.listlockunspent()
 510          assert_equal(len(locked_coins), 1)
 511          # Locked coins are automatically unlocked when manually selected
 512          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False)
 513          assert res["complete"]
 514  
 515          self.log.info("Replaceable...")
 516          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=True)
 517          assert res["complete"]
 518          assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "yes")
 519          res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=False)
 520          assert res["complete"]
 521          assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "no")
 522  
 523          self.log.info("Subtract fee from output")
 524          self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
 525  
 526          self.log.info("Include unsafe inputs")
 527          self.nodes[1].createwallet(wallet_name="w5")
 528          w5 = self.nodes[1].get_wallet_rpc("w5")
 529          self.test_send(from_wallet=w0, to_wallet=w5, amount=2)
 530          self.test_send(from_wallet=w5, to_wallet=w0, amount=1, expect_error=(-4, "Insufficient funds"))
 531          res = self.test_send(from_wallet=w5, to_wallet=w0, amount=1, include_unsafe=True)
 532          assert res["complete"]
 533  
 534          self.log.info("Minconf")
 535          self.nodes[1].createwallet(wallet_name="minconfw")
 536          minconfw= self.nodes[1].get_wallet_rpc("minconfw")
 537          self.test_send(from_wallet=w0, to_wallet=minconfw, amount=2)
 538          self.generate(self.nodes[0], 3)
 539          self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=4, expect_error=(-4, "Insufficient funds"))
 540          self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=-4, expect_error=(-8, "Negative minconf"))
 541          res = self.test_send(from_wallet=minconfw, to_wallet=w0, amount=1, minconf=3)
 542          assert res["complete"]
 543  
 544          self.log.info("External outputs")
 545          privkey, _ = generate_keypair(wif=True)
 546  
 547          self.nodes[1].createwallet("extsend")
 548          ext_wallet = self.nodes[1].get_wallet_rpc("extsend")
 549          self.nodes[1].createwallet("extfund")
 550          ext_fund = self.nodes[1].get_wallet_rpc("extfund")
 551  
 552          # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this
 553          desc = descsum_create("sh(wsh(pkh({})))".format(privkey))
 554          if self.options.descriptors:
 555              res = ext_fund.importdescriptors([{"desc": desc, "timestamp": "now"}])
 556          else:
 557              res = ext_fund.importmulti([{"desc": desc, "timestamp": "now"}])
 558          assert res[0]["success"]
 559          addr = self.nodes[0].deriveaddresses(desc)[0]
 560          addr_info = ext_fund.getaddressinfo(addr)
 561  
 562          self.nodes[0].sendtoaddress(addr, 10)
 563          self.nodes[0].sendtoaddress(ext_wallet.getnewaddress(), 10)
 564          self.generate(self.nodes[0], 6)
 565          ext_utxo = ext_fund.listunspent(addresses=[addr])[0]
 566  
 567          # An external input without solving data should result in an error
 568          self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"])))
 569  
 570          # But funding should work when the solving data is provided
 571          res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]})
 572          signed = ext_wallet.walletprocesspsbt(res["psbt"])
 573          signed = ext_fund.walletprocesspsbt(res["psbt"])
 574          assert signed["complete"]
 575  
 576          res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"descriptors": [desc]})
 577          signed = ext_wallet.walletprocesspsbt(res["psbt"])
 578          signed = ext_fund.walletprocesspsbt(res["psbt"])
 579          assert signed["complete"]
 580  
 581          dec = self.nodes[0].decodepsbt(signed["psbt"])
 582          for i, txin in enumerate(dec["tx"]["vin"]):
 583              if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]:
 584                  input_idx = i
 585                  break
 586          psbt_in = dec["inputs"][input_idx]
 587          scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
 588          witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
 589          input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
 590  
 591          # Input weight error conditions
 592          assert_raises_rpc_error(
 593              -8,
 594              "Input weights should be specified in inputs rather than in options.",
 595              ext_wallet.send,
 596              outputs={self.nodes[0].getnewaddress(): 15},
 597              options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]}
 598          )
 599  
 600          target_fee_rate_sat_vb = 10
 601          # Funding should also work when input weights are provided
 602          res = self.test_send(
 603              from_wallet=ext_wallet,
 604              to_wallet=self.nodes[0],
 605              amount=15,
 606              inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}],
 607              add_inputs=True,
 608              psbt=True,
 609              include_watching=True,
 610              fee_rate=target_fee_rate_sat_vb
 611          )
 612          signed = ext_wallet.walletprocesspsbt(res["psbt"])
 613          signed = ext_fund.walletprocesspsbt(res["psbt"])
 614          assert signed["complete"]
 615          testres = self.nodes[0].testmempoolaccept([signed["hex"]])[0]
 616          assert_equal(testres["allowed"], True)
 617          actual_fee_rate_sat_vb = Decimal(testres["fees"]["base"]) * Decimal(1e8) / Decimal(testres["vsize"])
 618          # Due to ECDSA signatures not always being the same length, the actual fee rate may be slightly different
 619          # but rounded to nearest integer, it should be the same as the target fee rate
 620          assert_equal(round(actual_fee_rate_sat_vb), target_fee_rate_sat_vb)
 621  
 622          # Check tx creation size limits
 623          self.test_weight_limits()
 624  
 625      def test_weight_limits(self):
 626          self.log.info("Test weight limits")
 627  
 628          self.nodes[1].createwallet("test_weight_limits")
 629          wallet = self.nodes[1].get_wallet_rpc("test_weight_limits")
 630  
 631          # Generate future inputs; 272 WU per input (273 when high-s).
 632          # Picking 1471 inputs will exceed the max standard tx weight.
 633          outputs = []
 634          for _ in range(1472):
 635              outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
 636          self.nodes[0].send(outputs=outputs)
 637          self.generate(self.nodes[0], 1)
 638  
 639          # 1) Try to fund transaction only using the preset inputs
 640          inputs = wallet.listunspent()
 641          assert_raises_rpc_error(-4, "Transaction too large",
 642                                  wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False})
 643  
 644          # 2) Let the wallet fund the transaction
 645          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",
 646                                  wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}])
 647  
 648          # 3) Pre-select some inputs and let the wallet fill-up the remaining amount
 649          inputs = inputs[0:1000]
 650          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",
 651                                  wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True})
 652  
 653          self.nodes[1].unloadwallet("test_weight_limits")
 654  
 655  
 656  if __name__ == '__main__':
 657      WalletSendTest(__file__).main()
 658