mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-22 20:58:09 +02:00
rpc: include_unsafe option for fundrawtransaction
Allow RPC users to opt-in to unsafe inputs when funding a raw transaction. Applications that need to manage a complex RBF flow (such as lightning nodes using anchor outputs) are very limited if they can only use safe inputs. Fixes #21299
This commit is contained in:
@@ -96,6 +96,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
self.test_option_subtract_fee_from_outputs()
|
||||
self.test_subtract_fee_with_presets()
|
||||
self.test_transaction_too_large()
|
||||
self.test_include_unsafe()
|
||||
|
||||
def test_change_position(self):
|
||||
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
|
||||
@@ -928,6 +929,40 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
self.nodes[0].generate(10)
|
||||
assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx)
|
||||
|
||||
def test_include_unsafe(self):
|
||||
self.log.info("Test fundrawtxn with unsafe inputs")
|
||||
|
||||
self.nodes[0].createwallet("unsafe")
|
||||
wallet = self.nodes[0].get_wallet_rpc("unsafe")
|
||||
|
||||
# We receive unconfirmed funds from external keys (unsafe outputs).
|
||||
addr = wallet.getnewaddress()
|
||||
txid1 = self.nodes[2].sendtoaddress(addr, 6)
|
||||
txid2 = self.nodes[2].sendtoaddress(addr, 4)
|
||||
self.sync_all()
|
||||
vout1 = find_vout_for_address(wallet, txid1, addr)
|
||||
vout2 = find_vout_for_address(wallet, txid2, addr)
|
||||
|
||||
# Unsafe inputs are ignored by default.
|
||||
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 5}])
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)
|
||||
|
||||
# But we can opt-in to use them for funding.
|
||||
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
|
||||
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
||||
assert any([txin['txid'] == txid1 and txin['vout'] == vout1 for txin in tx_dec['vin']])
|
||||
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
||||
wallet.sendrawtransaction(signedtx['hex'])
|
||||
|
||||
# And we can also use them once they're confirmed.
|
||||
self.nodes[0].generate(1)
|
||||
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 3}])
|
||||
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
|
||||
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
||||
assert any([txin['txid'] == txid2 and txin['vout'] == vout2 for txin in tx_dec['vin']])
|
||||
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
||||
wallet.sendrawtransaction(signedtx['hex'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RawTransactionsTest().main()
|
||||
|
||||
@@ -394,6 +394,14 @@ class PSBTTest(BitcoinTestFramework):
|
||||
# We don't care about the decode result, but decoding must succeed.
|
||||
self.nodes[0].decodepsbt(double_processed_psbt["psbt"])
|
||||
|
||||
# Make sure unsafe inputs are included if specified
|
||||
self.nodes[2].createwallet(wallet_name="unsafe")
|
||||
wunsafe = self.nodes[2].get_wallet_rpc("unsafe")
|
||||
self.nodes[0].sendtoaddress(wunsafe.getnewaddress(), 2)
|
||||
self.sync_mempools()
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", wunsafe.walletcreatefundedpsbt, [], [{self.nodes[0].getnewaddress(): 1}])
|
||||
wunsafe.walletcreatefundedpsbt([], [{self.nodes[0].getnewaddress(): 1}], 0, {"include_unsafe": True})
|
||||
|
||||
# BIP 174 Test Vectors
|
||||
|
||||
# Check that unknown values are just passed through
|
||||
|
||||
@@ -33,12 +33,15 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
||||
arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
|
||||
conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
|
||||
inputs=None, add_inputs=None, change_address=None, change_position=None, change_type=None,
|
||||
inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None, change_type=None,
|
||||
include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
|
||||
expect_error=None):
|
||||
assert (amount is None) != (data is None)
|
||||
|
||||
from_balance_before = from_wallet.getbalance()
|
||||
from_balance_before = from_wallet.getbalances()["mine"]["trusted"]
|
||||
if include_unsafe:
|
||||
from_balance_before += from_wallet.getbalances()["mine"]["untrusted_pending"]
|
||||
|
||||
if to_wallet is None:
|
||||
assert amount is None
|
||||
else:
|
||||
@@ -71,6 +74,8 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
options["inputs"] = inputs
|
||||
if add_inputs is not None:
|
||||
options["add_inputs"] = add_inputs
|
||||
if include_unsafe is not None:
|
||||
options["include_unsafe"] = include_unsafe
|
||||
if change_address is not None:
|
||||
options["change_address"] = change_address
|
||||
if change_position is not None:
|
||||
@@ -133,6 +138,10 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
assert not "txid" in res
|
||||
assert "psbt" in res
|
||||
|
||||
from_balance = from_wallet.getbalances()["mine"]["trusted"]
|
||||
if include_unsafe:
|
||||
from_balance += from_wallet.getbalances()["mine"]["untrusted_pending"]
|
||||
|
||||
if add_to_wallet and not include_watching:
|
||||
# Ensure transaction exists in the wallet:
|
||||
tx = from_wallet.gettransaction(res["txid"])
|
||||
@@ -143,13 +152,13 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
assert tx
|
||||
if amount:
|
||||
if subtract_fee_from_outputs:
|
||||
assert_equal(from_balance_before - from_wallet.getbalance(), amount)
|
||||
assert_equal(from_balance_before - from_balance, amount)
|
||||
else:
|
||||
assert_greater_than(from_balance_before - from_wallet.getbalance(), amount)
|
||||
assert_greater_than(from_balance_before - from_balance, amount)
|
||||
else:
|
||||
assert next((out for out in tx["vout"] if out["scriptPubKey"]["asm"] == "OP_RETURN 35"), None)
|
||||
else:
|
||||
assert_equal(from_balance_before, from_wallet.getbalance())
|
||||
assert_equal(from_balance_before, from_balance)
|
||||
|
||||
if to_wallet:
|
||||
self.sync_mempools()
|
||||
@@ -440,6 +449,14 @@ class WalletSendTest(BitcoinTestFramework):
|
||||
self.log.info("Subtract fee from output")
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
|
||||
|
||||
self.log.info("Include unsafe inputs")
|
||||
self.nodes[1].createwallet(wallet_name="w5")
|
||||
w5 = self.nodes[1].get_wallet_rpc("w5")
|
||||
self.test_send(from_wallet=w0, to_wallet=w5, amount=2)
|
||||
self.test_send(from_wallet=w5, to_wallet=w0, amount=1, expect_error=(-4, "Insufficient funds"))
|
||||
res = self.test_send(from_wallet=w5, to_wallet=w0, amount=1, include_unsafe=True)
|
||||
assert res["complete"]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletSendTest().main()
|
||||
|
||||
Reference in New Issue
Block a user