Skip to content

Commit f98872f

Browse files
committed
Merge #18244: rpc: fundrawtransaction and walletcreatefundedpsbt also lock manually selected coins
6d1f513 [rpc] fundrawtransaction, walletcreatefundedpsbt lock manually selected coins (Sjors Provoost) Pull request description: When using `fundrawtransaction` and `walletcreatefundedpsbt` with `lockUnspents`, it would only lock automatically selected coins, not manually selected coins. That doesn't make much sense to me if the goal is to prevent accidentally double-spending yourself before you broadcast a transaction. Note that when creating a transaction, manually selected coins are automatic "unlocked" (or more accurately: the lock is ignored). Earlier versions of this PR introduced an error when a locked coin is manually selected, but this idea was abandoned after some discussion. An application that uses this RPC should either rely on automatic coin selection (with `lockUnspents`) or handle lock concurrency itself with manual coin selection. In particular it needs to make sure to avoid/pause calls with automatic coin selection between calling `lockunspent` and the subsequent spending RPC. See #7518 for historical background. ACKs for top commit: meshcollider: Code review ACK 6d1f513 fjahr: Code review ACK 6d1f513 Tree-SHA512: 8773c788d92f2656952e1beac147ba9956b8c5132d474e0880e4c89ff53642928b4cbfcd1cb3d17798b9284f02618a8830c93a9f7a4733e5bded96adff1d5d4d
2 parents 7721b31 + 6d1f513 commit f98872f

File tree

5 files changed

+32
-6
lines changed

5 files changed

+32
-6
lines changed

doc/release-notes-18244.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Updated RPCs
2+
------------
3+
4+
- `fundrawtransaction` and `walletcreatefundedpsbt` when used with the `lockUnspents`
5+
argument now lock manually selected coins, in addition to automatically selected
6+
coins. Note that locked coins are never used in automatic coin selection, but
7+
can still be manually selected.

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,7 @@ static UniValue lockunspent(const JSONRPCRequest& request)
20612061
"Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n"
20622062
"If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n"
20632063
"A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n"
2064+
"Manually selected coins are automatically unlocked.\n"
20642065
"Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n"
20652066
"is always cleared (by virtue of process exit) when a node stops or fails.\n"
20662067
"Also see the listunspent call\n",

src/wallet/wallet.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2582,10 +2582,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
25822582
if (!coinControl.IsSelected(txin.prevout)) {
25832583
tx.vin.push_back(txin);
25842584

2585-
if (lockUnspents) {
2586-
LockCoin(txin.prevout);
2587-
}
25882585
}
2586+
if (lockUnspents) {
2587+
LockCoin(txin.prevout);
2588+
}
2589+
25892590
}
25902591

25912592
return true;

test/functional/rpc_psbt.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,16 @@ def run_test(self):
103103
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
104104
self.nodes[0].sendrawtransaction(final_tx)
105105

106-
# Get pubkeys
106+
# Manually selected inputs can be locked:
107+
assert_equal(len(self.nodes[0].listlockunspent()), 0)
108+
utxo1 = self.nodes[0].listunspent()[0]
109+
psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():1}, 0,{"lockUnspents": True})["psbt"]
110+
assert_equal(len(self.nodes[0].listlockunspent()), 1)
111+
112+
# Locks are ignored for manually selected inputs
113+
self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():1}, 0)
114+
115+
# Create p2sh, p2wpkh, and p2wsh addresses
107116
pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey']
108117
pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
109118
pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey']

test/functional/wallet_basic.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,19 @@ def run_test(self):
135135
self.nodes[2].lockunspent, False,
136136
[{"txid": unspent_0["txid"], "vout": 999}])
137137

138-
# An output should be unlocked when spent
138+
# The lock on a manually selected output is ignored
139139
unspent_0 = self.nodes[1].listunspent()[0]
140140
self.nodes[1].lockunspent(False, [unspent_0])
141141
tx = self.nodes[1].createrawtransaction([unspent_0], { self.nodes[1].getnewaddress() : 1 })
142-
tx = self.nodes[1].fundrawtransaction(tx)['hex']
142+
self.nodes[1].fundrawtransaction(tx,{"lockUnspents": True})
143+
144+
# fundrawtransaction can lock an input
145+
self.nodes[1].lockunspent(True, [unspent_0])
146+
assert_equal(len(self.nodes[1].listlockunspent()), 0)
147+
tx = self.nodes[1].fundrawtransaction(tx,{"lockUnspents": True})['hex']
148+
assert_equal(len(self.nodes[1].listlockunspent()), 1)
149+
150+
# Send transaction
143151
tx = self.nodes[1].signrawtransactionwithwallet(tx)["hex"]
144152
self.nodes[1].sendrawtransaction(tx)
145153
assert_equal(len(self.nodes[1].listlockunspent()), 0)

0 commit comments

Comments
 (0)