Changeset View
Changeset View
Standalone View
Standalone View
test/functional/wallet_send.py
Show All 15 Lines | |||||
) | ) | ||||
class WalletSendTest(BitcoinTestFramework): | class WalletSendTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
# whitelist all peers to speed up tx relay / mempool sync | # whitelist all peers to speed up tx relay / mempool sync | ||||
self.extra_args = [ | self.extra_args = [ | ||||
["-whitelist=127.0.0.1", ], | [ | ||||
["-whitelist=127.0.0.1", ], | "-whitelist=127.0.0.1", | ||||
], | |||||
[ | |||||
"-whitelist=127.0.0.1", | |||||
], | |||||
] | ] | ||||
def skip_test_if_missing_module(self): | def skip_test_if_missing_module(self): | ||||
self.skip_if_no_wallet() | self.skip_if_no_wallet() | ||||
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None, | def test_send( | ||||
add_to_wallet=None, psbt=None, inputs=None, add_inputs=None, | self, | ||||
change_address=None, change_position=None, | from_wallet, | ||||
include_watching=None, locktime=None, lock_unspents=None, | to_wallet=None, | ||||
subtract_fee_from_outputs=None, fee_rate=None, | amount=None, | ||||
expect_error=None): | data=None, | ||||
add_to_wallet=None, | |||||
psbt=None, | |||||
inputs=None, | |||||
add_inputs=None, | |||||
change_address=None, | |||||
change_position=None, | |||||
include_watching=None, | |||||
locktime=None, | |||||
lock_unspents=None, | |||||
subtract_fee_from_outputs=None, | |||||
fee_rate=None, | |||||
expect_error=None, | |||||
): | |||||
assert (amount is None) != (data is None) | assert (amount is None) != (data is None) | ||||
from_balance_before = from_wallet.getbalance() | from_balance_before = from_wallet.getbalance() | ||||
if to_wallet is None: | if to_wallet is None: | ||||
assert amount is None | assert amount is None | ||||
else: | else: | ||||
to_untrusted_pending_before = \ | to_untrusted_pending_before = to_wallet.getbalances()["mine"][ | ||||
to_wallet.getbalances()["mine"]["untrusted_pending"] | "untrusted_pending" | ||||
] | |||||
if amount: | if amount: | ||||
dest = to_wallet.getnewaddress() | dest = to_wallet.getnewaddress() | ||||
outputs = {dest: amount} | outputs = {dest: amount} | ||||
else: | else: | ||||
outputs = {"data": data} | outputs = {"data": data} | ||||
# Construct options dictionary | # Construct options dictionary | ||||
options = {} | options = {} | ||||
if add_to_wallet is not None: | if add_to_wallet is not None: | ||||
options["add_to_wallet"] = add_to_wallet | options["add_to_wallet"] = add_to_wallet | ||||
else: | else: | ||||
add_to_wallet = ( | add_to_wallet = ( | ||||
False if psbt else | False if psbt else from_wallet.getwalletinfo()["private_keys_enabled"] | ||||
from_wallet.getwalletinfo()["private_keys_enabled"] | |||||
) | ) | ||||
if psbt is not None: | if psbt is not None: | ||||
options["psbt"] = psbt | options["psbt"] = psbt | ||||
if inputs is not None: | if inputs is not None: | ||||
options["inputs"] = inputs | options["inputs"] = inputs | ||||
if add_inputs is not None: | if add_inputs is not None: | ||||
options["add_inputs"] = add_inputs | options["add_inputs"] = add_inputs | ||||
if change_address is not None: | if change_address is not None: | ||||
Show All 10 Lines | ): | ||||
options["subtract_fee_from_outputs"] = subtract_fee_from_outputs | options["subtract_fee_from_outputs"] = subtract_fee_from_outputs | ||||
if fee_rate is not None: | if fee_rate is not None: | ||||
options["fee_rate"] = fee_rate | options["fee_rate"] = fee_rate | ||||
if len(options.keys()) == 0: | if len(options.keys()) == 0: | ||||
options = None | options = None | ||||
if expect_error is None: | if expect_error is None: | ||||
res = from_wallet.send( | res = from_wallet.send(outputs=outputs, options=options) | ||||
outputs=outputs, | |||||
options=options) | |||||
else: | else: | ||||
try: | try: | ||||
assert_raises_rpc_error( | assert_raises_rpc_error( | ||||
expect_error[0], expect_error[1], from_wallet.send, | expect_error[0], | ||||
outputs=outputs, options=options) | expect_error[1], | ||||
from_wallet.send, | |||||
outputs=outputs, | |||||
options=options, | |||||
) | |||||
except AssertionError: | except AssertionError: | ||||
# Provide debug info if the test fails | # Provide debug info if the test fails | ||||
self.log.error("Unexpected successful result:") | self.log.error("Unexpected successful result:") | ||||
self.log.error(options) | self.log.error(options) | ||||
res = from_wallet.send( | res = from_wallet.send(outputs=outputs, options=options) | ||||
outputs=outputs, | |||||
options=options) | |||||
self.log.error(res) | self.log.error(res) | ||||
if "txid" in res and add_to_wallet: | if "txid" in res and add_to_wallet: | ||||
self.log.error("Transaction details:") | self.log.error("Transaction details:") | ||||
try: | try: | ||||
tx = from_wallet.gettransaction(res["txid"]) | tx = from_wallet.gettransaction(res["txid"]) | ||||
self.log.error(tx) | self.log.error(tx) | ||||
self.log.error( | self.log.error( | ||||
"testmempoolaccept (transaction may already be in mempool):") | "testmempoolaccept (transaction may already be in mempool):" | ||||
self.log.error( | ) | ||||
from_wallet.testmempoolaccept([tx["hex"]])) | self.log.error(from_wallet.testmempoolaccept([tx["hex"]])) | ||||
except JSONRPCException as exc: | except JSONRPCException as exc: | ||||
self.log.error(exc) | self.log.error(exc) | ||||
raise | raise | ||||
return | return | ||||
if locktime: | if locktime: | ||||
return res | return res | ||||
if (from_wallet.getwalletinfo()["private_keys_enabled"] | if from_wallet.getwalletinfo()["private_keys_enabled"] and not include_watching: | ||||
and not include_watching): | |||||
assert_equal(res["complete"], True) | assert_equal(res["complete"], True) | ||||
assert "txid" in res | assert "txid" in res | ||||
else: | else: | ||||
assert_equal(res["complete"], False) | assert_equal(res["complete"], False) | ||||
assert "txid" not in res | assert "txid" not in res | ||||
assert "psbt" in res | assert "psbt" in res | ||||
if add_to_wallet and not include_watching: | if add_to_wallet and not include_watching: | ||||
# Ensure transaction exists in the wallet: | # Ensure transaction exists in the wallet: | ||||
tx = from_wallet.gettransaction(res["txid"]) | tx = from_wallet.gettransaction(res["txid"]) | ||||
assert tx | assert tx | ||||
# Ensure transaction exists in the mempool: | # Ensure transaction exists in the mempool: | ||||
tx = from_wallet.getrawtransaction(res["txid"], True) | tx = from_wallet.getrawtransaction(res["txid"], True) | ||||
assert tx | assert tx | ||||
if amount: | if amount: | ||||
if subtract_fee_from_outputs: | if subtract_fee_from_outputs: | ||||
assert_equal( | assert_equal(from_balance_before - from_wallet.getbalance(), amount) | ||||
from_balance_before - | |||||
from_wallet.getbalance(), | |||||
amount) | |||||
else: | else: | ||||
assert_greater_than( | assert_greater_than( | ||||
from_balance_before - from_wallet.getbalance(), amount) | from_balance_before - from_wallet.getbalance(), amount | ||||
) | |||||
else: | else: | ||||
assert next( | assert next( | ||||
(out for out in tx["vout"] if out["scriptPubKey"] | ( | ||||
["asm"] == "OP_RETURN 35"), | out | ||||
None) | for out in tx["vout"] | ||||
if out["scriptPubKey"]["asm"] == "OP_RETURN 35" | |||||
), | |||||
None, | |||||
) | |||||
else: | else: | ||||
assert_equal(from_balance_before, from_wallet.getbalance()) | assert_equal(from_balance_before, from_wallet.getbalance()) | ||||
if to_wallet: | if to_wallet: | ||||
self.sync_mempools() | self.sync_mempools() | ||||
if add_to_wallet: | if add_to_wallet: | ||||
if not subtract_fee_from_outputs: | if not subtract_fee_from_outputs: | ||||
assert_equal( | assert_equal( | ||||
to_wallet.getbalances()["mine"]["untrusted_pending"], | to_wallet.getbalances()["mine"]["untrusted_pending"], | ||||
to_untrusted_pending_before + | to_untrusted_pending_before + Decimal(amount if amount else 0), | ||||
Decimal( | ) | ||||
amount if amount else 0)) | |||||
else: | else: | ||||
assert_equal( | assert_equal( | ||||
to_wallet.getbalances()["mine"]["untrusted_pending"], | to_wallet.getbalances()["mine"]["untrusted_pending"], | ||||
to_untrusted_pending_before) | to_untrusted_pending_before, | ||||
) | |||||
return res | return res | ||||
def run_test(self): | def run_test(self): | ||||
self.log.info("Setup wallets...") | self.log.info("Setup wallets...") | ||||
# w0 is a wallet with coinbase rewards | # w0 is a wallet with coinbase rewards | ||||
w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) | w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) | ||||
# w1 is a regular wallet | # w1 is a regular wallet | ||||
self.nodes[1].createwallet(wallet_name="w1") | self.nodes[1].createwallet(wallet_name="w1") | ||||
w1 = self.nodes[1].get_wallet_rpc("w1") | w1 = self.nodes[1].get_wallet_rpc("w1") | ||||
# w2 contains the private keys for w3 | # w2 contains the private keys for w3 | ||||
self.nodes[1].createwallet(wallet_name="w2") | self.nodes[1].createwallet(wallet_name="w2") | ||||
w2 = self.nodes[1].get_wallet_rpc("w2") | w2 = self.nodes[1].get_wallet_rpc("w2") | ||||
# w3 is a watch-only wallet, based on w2 | # w3 is a watch-only wallet, based on w2 | ||||
self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True) | self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True) | ||||
w3 = self.nodes[1].get_wallet_rpc("w3") | w3 = self.nodes[1].get_wallet_rpc("w3") | ||||
for _ in range(3): | for _ in range(3): | ||||
a2_receive = w2.getnewaddress() | a2_receive = w2.getnewaddress() | ||||
# doesn't actually use change derivation | # doesn't actually use change derivation | ||||
a2_change = w2.getrawchangeaddress() | a2_change = w2.getrawchangeaddress() | ||||
res = w3.importmulti([{ | res = w3.importmulti( | ||||
[ | |||||
{ | |||||
"desc": w2.getaddressinfo(a2_receive)["desc"], | "desc": w2.getaddressinfo(a2_receive)["desc"], | ||||
"timestamp": "now", | "timestamp": "now", | ||||
"keypool": True, | "keypool": True, | ||||
"watchonly": True | "watchonly": True, | ||||
}, { | }, | ||||
{ | |||||
"desc": w2.getaddressinfo(a2_change)["desc"], | "desc": w2.getaddressinfo(a2_change)["desc"], | ||||
"timestamp": "now", | "timestamp": "now", | ||||
"keypool": True, | "keypool": True, | ||||
"internal": True, | "internal": True, | ||||
"watchonly": True | "watchonly": True, | ||||
}]) | }, | ||||
] | |||||
) | |||||
assert_equal(res, [{"success": True}, {"success": True}]) | assert_equal(res, [{"success": True}, {"success": True}]) | ||||
# fund w3 | # fund w3 | ||||
w0.sendtoaddress(a2_receive, 10_000_000) | w0.sendtoaddress(a2_receive, 10_000_000) | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
# w4 has private keys enabled, but only contains watch-only keys (from | # w4 has private keys enabled, but only contains watch-only keys (from | ||||
# w2) | # w2) | ||||
self.nodes[1].createwallet( | self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False) | ||||
wallet_name="w4", | |||||
disable_private_keys=False) | |||||
w4 = self.nodes[1].get_wallet_rpc("w4") | w4 = self.nodes[1].get_wallet_rpc("w4") | ||||
for _ in range(3): | for _ in range(3): | ||||
a2_receive = w2.getnewaddress() | a2_receive = w2.getnewaddress() | ||||
res = w4.importmulti([{ | res = w4.importmulti( | ||||
[ | |||||
{ | |||||
"desc": w2.getaddressinfo(a2_receive)["desc"], | "desc": w2.getaddressinfo(a2_receive)["desc"], | ||||
"timestamp": "now", | "timestamp": "now", | ||||
"keypool": False, | "keypool": False, | ||||
"watchonly": True | "watchonly": True, | ||||
}]) | } | ||||
] | |||||
) | |||||
assert_equal(res, [{"success": True}]) | assert_equal(res, [{"success": True}]) | ||||
# fund w4 | # fund w4 | ||||
w0.sendtoaddress(a2_receive, 10_000_000) | w0.sendtoaddress(a2_receive, 10_000_000) | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
self.log.info("Send to address...") | self.log.info("Send to address...") | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) | self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) | ||||
self.test_send( | self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=True | ||||
to_wallet=w1, | ) | ||||
amount=1_000_000, | |||||
add_to_wallet=True) | |||||
self.log.info("Don't broadcast...") | self.log.info("Don't broadcast...") | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False | ||||
to_wallet=w1, | ) | ||||
amount=1_000_000, | |||||
add_to_wallet=False) | |||||
assert res["hex"] | assert res["hex"] | ||||
self.log.info("Return PSBT...") | self.log.info("Return PSBT...") | ||||
res = self.test_send( | res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, psbt=True) | ||||
from_wallet=w0, | |||||
to_wallet=w1, | |||||
amount=1_000_000, | |||||
psbt=True) | |||||
assert res["psbt"] | assert res["psbt"] | ||||
self.log.info( | self.log.info( | ||||
"Create transaction that spends to address, but don't broadcast...") | "Create transaction that spends to address, but don't broadcast..." | ||||
) | |||||
self.test_send( | self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False | ||||
to_wallet=w1, | ) | ||||
amount=1_000_000, | |||||
add_to_wallet=False) | |||||
self.log.info("Create PSBT from watch-only wallet w3, sign with w2...") | self.log.info("Create PSBT from watch-only wallet w3, sign with w2...") | ||||
res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1_000_000) | res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1_000_000) | ||||
res = w2.walletprocesspsbt(res["psbt"]) | res = w2.walletprocesspsbt(res["psbt"]) | ||||
assert res["complete"] | assert res["complete"] | ||||
self.log.info( | self.log.info( | ||||
"Create PSBT from wallet w4 with watch-only keys, sign with w2...") | "Create PSBT from wallet w4 with watch-only keys, sign with w2..." | ||||
self.test_send(from_wallet=w4, to_wallet=w1, amount=1_000_000, | ) | ||||
expect_error=(-4, "Insufficient funds")) | self.test_send( | ||||
from_wallet=w4, | |||||
to_wallet=w1, | |||||
amount=1_000_000, | |||||
expect_error=(-4, "Insufficient funds"), | |||||
) | |||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w4, | from_wallet=w4, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
include_watching=True, | include_watching=True, | ||||
add_to_wallet=False) | add_to_wallet=False, | ||||
) | |||||
res = w2.walletprocesspsbt(res["psbt"]) | res = w2.walletprocesspsbt(res["psbt"]) | ||||
assert res["complete"] | assert res["complete"] | ||||
self.log.info("Create OP_RETURN...") | self.log.info("Create OP_RETURN...") | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) | self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) | ||||
self.test_send(from_wallet=w0, | self.test_send( | ||||
from_wallet=w0, | |||||
data="Hello World", | data="Hello World", | ||||
expect_error=(-8, | expect_error=(-8, "Data must be hexadecimal string (not 'Hello World')"), | ||||
"Data must be hexadecimal string (not 'Hello World')")) | ) | ||||
self.test_send(from_wallet=w0, data="23") | self.test_send(from_wallet=w0, data="23") | ||||
res = self.test_send(from_wallet=w3, data="23") | res = self.test_send(from_wallet=w3, data="23") | ||||
res = w2.walletprocesspsbt(res["psbt"]) | res = w2.walletprocesspsbt(res["psbt"]) | ||||
assert res["complete"] | assert res["complete"] | ||||
self.log.info("Set fee rate...") | self.log.info("Set fee rate...") | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
fee_rate=Decimal("20.00"), | fee_rate=Decimal("20.00"), | ||||
add_to_wallet=False) | add_to_wallet=False, | ||||
) | |||||
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] | fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] | ||||
assert_fee_amount(fee, | assert_fee_amount(fee, len(res["hex"]) // 2, Decimal("20.00")) | ||||
len(res["hex"]) // 2, | self.test_send( | ||||
Decimal("20.00")) | from_wallet=w0, | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, fee_rate=-1, | to_wallet=w1, | ||||
expect_error=(-3, "Amount out of range")) | amount=1_000_000, | ||||
fee_rate=-1, | |||||
expect_error=(-3, "Amount out of range"), | |||||
) | |||||
# Fee rate of 0.1 satoshi per byte should throw an error | # Fee rate of 0.1 satoshi per byte should throw an error | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, | self.test_send( | ||||
from_wallet=w0, | |||||
to_wallet=w1, | |||||
amount=1_000_000, | |||||
fee_rate=Decimal("1.00"), | fee_rate=Decimal("1.00"), | ||||
expect_error=(-4, "Fee rate (1.00 XEC/kB) is lower than the minimum fee rate setting (10.00 XEC/kB)")) | expect_error=( | ||||
-4, | |||||
( | |||||
"Fee rate (1.00 XEC/kB) is lower than the minimum fee rate setting" | |||||
" (10.00 XEC/kB)" | |||||
), | |||||
), | |||||
) | |||||
# TODO: Return hex if fee rate is below -maxmempool | # TODO: Return hex if fee rate is below -maxmempool | ||||
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, | # res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, | ||||
# feeRate=Decimal("1.00"), add_to_wallet=False) | # feeRate=Decimal("1.00"), add_to_wallet=False) | ||||
# assert res["hex"] | # assert res["hex"] | ||||
# hex = res["hex"] | # hex = res["hex"] | ||||
# res = self.nodes[0].testmempoolaccept([hex]) | # res = self.nodes[0].testmempoolaccept([hex]) | ||||
# assert not res[0]["allowed"] | # assert not res[0]["allowed"] | ||||
# assert_equal(res[0]["reject-reason"], "...") # low fee | # assert_equal(res[0]["reject-reason"], "...") # low fee | ||||
# assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("1.00")) | # assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("1.00")) | ||||
self.log.info( | self.log.info("If inputs are specified, do not automatically add more...") | ||||
"If inputs are specified, do not automatically add more...") | |||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=51_000_000, | amount=51_000_000, | ||||
inputs=[], | inputs=[], | ||||
add_to_wallet=False) | add_to_wallet=False, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
utxo1 = w0.listunspent()[0] | utxo1 = w0.listunspent()[0] | ||||
assert_equal(utxo1["amount"], 50_000_000) | assert_equal(utxo1["amount"], 50_000_000) | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=51_000_000, | self.test_send( | ||||
inputs=[utxo1], expect_error=(-4, "Insufficient funds")) | from_wallet=w0, | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=51_000_000, | to_wallet=w1, | ||||
inputs=[utxo1], add_inputs=False, | amount=51_000_000, | ||||
expect_error=(-4, "Insufficient funds")) | inputs=[utxo1], | ||||
expect_error=(-4, "Insufficient funds"), | |||||
) | |||||
self.test_send( | |||||
from_wallet=w0, | |||||
to_wallet=w1, | |||||
amount=51_000_000, | |||||
inputs=[utxo1], | |||||
add_inputs=False, | |||||
expect_error=(-4, "Insufficient funds"), | |||||
) | |||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=51_000_000, | amount=51_000_000, | ||||
inputs=[utxo1], | inputs=[utxo1], | ||||
add_inputs=True, | add_inputs=True, | ||||
add_to_wallet=False) | add_to_wallet=False, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
self.log.info("Manual change address and position...") | self.log.info("Manual change address and position...") | ||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, change_address="not an address", | self.test_send( | ||||
expect_error=(-5, "Change address must be a valid bitcoin address")) | from_wallet=w0, | ||||
to_wallet=w1, | |||||
amount=1_000_000, | |||||
change_address="not an address", | |||||
expect_error=(-5, "Change address must be a valid bitcoin address"), | |||||
) | |||||
change_address = w0.getnewaddress() | change_address = w0.getnewaddress() | ||||
self.test_send( | self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
add_to_wallet=False, | add_to_wallet=False, | ||||
change_address=change_address) | change_address=change_address, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
add_to_wallet=False, | add_to_wallet=False, | ||||
change_address=change_address, | change_address=change_address, | ||||
change_position=0) | change_position=0, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].decodepsbt( | self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"][ | ||||
res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["addresses"], | "addresses" | ||||
[change_address]) | ], | ||||
[change_address], | |||||
) | |||||
self.log.info("Set lock time...") | self.log.info("Set lock time...") | ||||
height = self.nodes[0].getblockchaininfo()["blocks"] | height = self.nodes[0].getblockchaininfo()["blocks"] | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, to_wallet=w1, amount=1_000_000, locktime=height + 1 | ||||
to_wallet=w1, | ) | ||||
amount=1_000_000, | |||||
locktime=height + 1) | |||||
assert res["complete"] | assert res["complete"] | ||||
assert res["txid"] | assert res["txid"] | ||||
txid = res["txid"] | txid = res["txid"] | ||||
# Although the wallet finishes the transaction, it can't be added to | # Although the wallet finishes the transaction, it can't be added to | ||||
# the mempool yet: | # the mempool yet: | ||||
tx_hex = self.nodes[0].gettransaction(res["txid"])["hex"] | tx_hex = self.nodes[0].gettransaction(res["txid"])["hex"] | ||||
res = self.nodes[0].testmempoolaccept([tx_hex]) | res = self.nodes[0].testmempoolaccept([tx_hex]) | ||||
assert not res[0]["allowed"] | assert not res[0]["allowed"] | ||||
Show All 13 Lines | def run_test(self): | ||||
utxo1 = w0.listunspent()[0] | utxo1 = w0.listunspent()[0] | ||||
assert_greater_than(utxo1["amount"], 1_000_000) | assert_greater_than(utxo1["amount"], 1_000_000) | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
inputs=[utxo1], | inputs=[utxo1], | ||||
add_to_wallet=False, | add_to_wallet=False, | ||||
lock_unspents=True) | lock_unspents=True, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
locked_coins = w0.listlockunspent() | locked_coins = w0.listlockunspent() | ||||
assert_equal(len(locked_coins), 1) | assert_equal(len(locked_coins), 1) | ||||
# Locked coins are automatically unlocked when manually selected | # Locked coins are automatically unlocked when manually selected | ||||
res = self.test_send( | res = self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
inputs=[utxo1], | inputs=[utxo1], | ||||
add_to_wallet=False) | add_to_wallet=False, | ||||
) | |||||
assert res["complete"] | assert res["complete"] | ||||
self.log.info("Subtract fee from output") | self.log.info("Subtract fee from output") | ||||
self.test_send( | self.test_send( | ||||
from_wallet=w0, | from_wallet=w0, | ||||
to_wallet=w1, | to_wallet=w1, | ||||
amount=1_000_000, | amount=1_000_000, | ||||
subtract_fee_from_outputs=[0]) | subtract_fee_from_outputs=[0], | ||||
) | |||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
WalletSendTest().main() | WalletSendTest().main() |