Skip to content

Commit 73a5f5b

Browse files
rustyrussellcdecker
authored andcommitted
fundpsbt: make parameters more usable.
fundpsbt forces the caller to manually add their weight * feerate to the satoshis they ask for. That means no named feerates. Instead, create a startweight parameter and do the calc for them internally, and return the feerate we used (and, while we're at it, the estimated final weight). This API change is best done now, as it would otherwise have to be appended as a parameter. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent fe119fc commit 73a5f5b

File tree

5 files changed

+110
-34
lines changed

5 files changed

+110
-34
lines changed

contrib/pyln-client/pyln/client/lightning.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,13 +1126,14 @@ def unreserveinputs(self, psbt):
11261126
}
11271127
return self.call("unreserveinputs", payload)
11281128

1129-
def fundpsbt(self, satoshi, feerate, minconf=None, reserve=True):
1129+
def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True):
11301130
"""
11311131
Create a PSBT with inputs sufficient to give an output of satoshi.
11321132
"""
11331133
payload = {
11341134
"satoshi": satoshi,
11351135
"feerate": feerate,
1136+
"startweight": startweight,
11361137
"minconf": minconf,
11371138
"reserve": reserve,
11381139
}

doc/lightning-fundpsbt.7

Lines changed: 37 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/lightning-fundpsbt.7.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ lightning-fundpsbt -- Command to populate PSBT inputs from the wallet
44
SYNOPSIS
55
--------
66

7-
**fundpsbt** *satoshi* *feerate* \[*minconf*\] \[*reserve*\]
7+
**fundpsbt** *satoshi* *feerate* *startweight* \[*minconf*\] \[*reserve*\]
88

99
DESCRIPTION
1010
-----------
@@ -18,30 +18,54 @@ be a whole number, a whole number ending in *sat*, a whole number
1818
ending in *000msat*, or a number with 1 to 8 decimal places ending in
1919
*btc*.
2020

21-
You calculate the value by starting with the amount you want to pay
22-
and adding the fee which will be needed to pay for the base of the
23-
transaction plus that output, and any other outputs and inputs you
24-
will add to the final transaction.
21+
*feerate* can be one of the feerates listed in lightning-feerates(7),
22+
or one of the strings *urgent* (aim for next block), *normal* (next 4
23+
blocks or so) or *slow* (next 100 blocks or so) to use lightningd’s
24+
internal estimates. It can also be a *feerate* is a number, with an
25+
optional suffix: *perkw* means the number is interpreted as
26+
satoshi-per-kilosipa (weight), and *perkb* means it is interpreted
27+
bitcoind-style as satoshi-per-kilobyte. Omitting the suffix is
28+
equivalent to *perkb*.
2529

26-
*feerate* is a number, with an optional suffix: *perkw* means the
27-
number is interpreted as satoshi-per-kilosipa (weight), and *perkb*
28-
means it is interpreted bitcoind-style as
29-
satoshi-per-kilobyte. Omitting the suffix is equivalent to *perkb*.
30+
*startweight* is the weight of the transaction before *fundpsbt* has
31+
added any inputs.
3032

3133
*minconf* specifies the minimum number of confirmations that used
3234
outputs should have. Default is 1.
3335

3436
*reserve* is a boolean: if true (the default), then *reserveinputs* is
3537
called (successfully, with *exclusive* true) on the returned PSBT.
3638

39+
EXAMPLE USAGE
40+
-------------
41+
42+
Let's assume the caller is trying to produce a 100,000 satoshi output.
43+
44+
First, the caller estimates the weight of the core (typically 42) and
45+
known outputs of the transaction (typically (9 + scriptlen) * 4). For
46+
a simple P2WPKH it's a 22 byte scriptpubkey, so that's 164 weight.
47+
48+
It calls "*fundpsbt* 100000sat slow 206", which succeeds, and returns
49+
the *psbt* and *feerate_per_kw* it used, the *estimated_final_weight*
50+
and any *excess_msat*.
51+
52+
If *excess_msat* is greater than the cost of adding a change output,
53+
the caller adds a change output randomly to position 0 or 1 in the
54+
PSBT. Say *feerate_per_kw* is 253, and the change output is a P2WPKH
55+
(weight 164), that would cost the cost is around 41 sats. With the
56+
dust limit disallowing payments below 546 satoshis, we would only create
57+
a change output if *excess_msat* was greater or equal to 41 + 546.
58+
3759
RETURN VALUE
3860
------------
3961

40-
On success, returns the *psbt* containing the inputs, and
62+
On success, returns the *psbt* containing the inputs, *feerate_per_kw*
63+
showing the exact numeric feerate it used, *estimated_final_weight* for
64+
the estimated weight of the transaction once fully signed, and
4165
*excess_msat* containing the amount above *satoshi* which is
4266
available. This could be zero, or dust. If *satoshi* was "all",
4367
then *excess_msat* is the entire amount once fees are subtracted
44-
for the weights of the inputs.
68+
for the weights of the inputs and startweight.
4569

4670
If *reserve* was true, then a *reservations* array is returned,
4771
exactly like *reserveinputs*.

tests/test_wallet.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -506,41 +506,53 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
506506
feerate = '7500perkw'
507507

508508
# Should get one input, plus some excess
509-
funding = l1.rpc.fundpsbt(amount // 2, feerate, reserve=False)
509+
funding = l1.rpc.fundpsbt(amount // 2, feerate, 0, reserve=False)
510510
psbt = bitcoind.rpc.decodepsbt(funding['psbt'])
511511
assert len(psbt['tx']['vin']) == 1
512512
assert funding['excess_msat'] > Millisatoshi(0)
513513
assert funding['excess_msat'] < Millisatoshi(amount // 2 * 1000)
514+
assert funding['feerate_per_kw'] == 7500
515+
assert 'estimated_final_weight' in funding
516+
assert 'reservations' not in funding
517+
518+
# This should add 99 to the weight, but otherwise be identical (might choose different inputs though!)
519+
funding2 = l1.rpc.fundpsbt(amount // 2, feerate, 99, reserve=False)
520+
psbt2 = bitcoind.rpc.decodepsbt(funding2['psbt'])
521+
assert len(psbt2['tx']['vin']) == 1
522+
assert funding2['excess_msat'] < funding['excess_msat']
523+
assert funding2['feerate_per_kw'] == 7500
524+
# Naively you'd expect this to be +99, but it might have selected a non-p2sh output...
525+
assert funding2['estimated_final_weight'] > funding['estimated_final_weight']
514526

515527
# Cannot afford this one (too much)
516528
with pytest.raises(RpcError, match=r"not afford"):
517-
l1.rpc.fundpsbt(amount * total_outs, feerate)
529+
l1.rpc.fundpsbt(amount * total_outs, feerate, 0)
518530

519531
# Nor this (depth insufficient)
520532
with pytest.raises(RpcError, match=r"not afford"):
521-
l1.rpc.fundpsbt(amount // 2, feerate, minconf=2)
533+
l1.rpc.fundpsbt(amount // 2, feerate, 0, minconf=2)
522534

523535
# Should get two inputs.
524-
psbt = bitcoind.rpc.decodepsbt(l1.rpc.fundpsbt(amount, feerate, reserve=False)['psbt'])
536+
psbt = bitcoind.rpc.decodepsbt(l1.rpc.fundpsbt(amount, feerate, 0, reserve=False)['psbt'])
525537
assert len(psbt['tx']['vin']) == 2
526538

527539
# Should not use reserved outputs.
528540
psbt = bitcoind.rpc.createpsbt([{'txid': out[0], 'vout': out[1]} for out in outputs], [])
529541
l1.rpc.reserveinputs(psbt)
530542
with pytest.raises(RpcError, match=r"not afford"):
531-
l1.rpc.fundpsbt(amount // 2, feerate)
543+
l1.rpc.fundpsbt(amount // 2, feerate, 0)
532544

533545
# Will use first one if unreserved.
534546
l1.rpc.unreserveinputs(bitcoind.rpc.createpsbt([{'txid': outputs[0][0], 'vout': outputs[0][1]}], []))
535-
psbt = l1.rpc.fundpsbt(amount // 2, feerate)['psbt']
547+
psbt = l1.rpc.fundpsbt(amount // 2, feerate, 0)['psbt']
536548

537549
# Should have passed to reserveinputs.
538550
with pytest.raises(RpcError, match=r"already reserved"):
539551
l1.rpc.reserveinputs(psbt)
540552

541553
# And now we can't afford any more.
542554
with pytest.raises(RpcError, match=r"not afford"):
543-
l1.rpc.fundpsbt(amount // 2, feerate)
555+
l1.rpc.fundpsbt(amount // 2, feerate, 0)
544556

545557

546558
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
@@ -564,9 +576,10 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
564576
bitcoind.generate_block(1)
565577
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == total_outs)
566578

567-
# Make a PSBT out of our inputs (FIXME: satoshi amount should include fees!)
579+
# Make a PSBT out of our inputs
568580
funding = l1.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000),
569581
feerate=7500,
582+
startweight=42,
570583
reserve=True)
571584
assert len([x for x in l1.rpc.listfunds()['outputs'] if x['reserved']]) == 4
572585
psbt = bitcoind.rpc.decodepsbt(funding['psbt'])
@@ -628,6 +641,7 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
628641
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == total_outs)
629642
l2_funding = l2.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000),
630643
feerate=7500,
644+
startweight=42,
631645
reserve=True)
632646

633647
# Try to get L1 to sign it
@@ -637,6 +651,7 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
637651
# Add some of our own PSBT inputs to it
638652
l1_funding = l1.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000),
639653
feerate=7500,
654+
startweight=42,
640655
reserve=True)
641656

642657
# Join and add an output
@@ -652,6 +667,7 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
652667
# Send a PSBT that's not ours
653668
l2_funding = l2.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000),
654669
feerate=7500,
670+
startweight=42,
655671
reserve=True)
656672
psbt = bitcoind.rpc.joinpsbts([l2_funding['psbt'], output_pbst])
657673
l2_signed_psbt = l2.rpc.signpsbt(psbt)['signed_psbt']

wallet/reservation.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,16 @@ static struct command_result *json_fundpsbt(struct command *cmd,
184184
struct json_stream *response;
185185
struct utxo **utxos;
186186
u32 *feerate_per_kw;
187-
u32 *minconf;
187+
u32 *minconf, *weight;
188188
struct amount_sat *amount, input, needed, excess, total_fee;
189189
bool all, *reserve;
190190
u32 locktime, maxheight, current_height;
191191
struct bitcoin_tx *tx;
192192

193193
if (!param(cmd, buffer, params,
194194
p_req("satoshi", param_sat_or_all, &amount),
195-
p_req("feerate", param_feerate_val, &feerate_per_kw),
195+
p_req("feerate", param_feerate, &feerate_per_kw),
196+
p_req("startweight", param_number, &weight),
196197
p_opt_def("minconf", param_number, &minconf, 1),
197198
p_opt_def("reserve", param_bool, &reserve, true),
198199
NULL))
@@ -203,10 +204,15 @@ static struct command_result *json_fundpsbt(struct command *cmd,
203204

204205
current_height = get_block_height(cmd->ld->topology);
205206

207+
/* Can overflow if amount is "all" */
208+
if (!amount_sat_add(amount, *amount,
209+
amount_tx_fee(*feerate_per_kw, *weight)))
210+
;
211+
206212
/* We keep adding until we meet their output requirements. */
207213
input = AMOUNT_SAT(0);
208214
utxos = tal_arr(cmd, struct utxo *, 0);
209-
total_fee = AMOUNT_SAT(0);
215+
total_fee = amount_tx_fee(*feerate_per_kw, *weight);
210216
while (amount_sat_sub(&needed, *amount, input)
211217
&& !amount_sat_eq(needed, AMOUNT_SAT(0))) {
212218
struct utxo *utxo;
@@ -227,6 +233,7 @@ static struct command_result *json_fundpsbt(struct command *cmd,
227233
"impossible UTXO value");
228234

229235
/* But increase amount needed, to pay for new input */
236+
*weight += utxo_spend_weight(utxo);
230237
fee = amount_tx_fee(*feerate_per_kw,
231238
utxo_spend_weight(utxo));
232239
if (!amount_sat_add(amount, *amount, fee))
@@ -300,6 +307,8 @@ static struct command_result *json_fundpsbt(struct command *cmd,
300307

301308
response = json_stream_success(cmd);
302309
json_add_psbt(response, "psbt", tx->psbt);
310+
json_add_num(response, "feerate_per_kw", *feerate_per_kw);
311+
json_add_num(response, "estimated_final_weight", *weight);
303312
json_add_amount_sat_only(response, "excess_msat", excess);
304313
if (*reserve)
305314
reserve_and_report(response, cmd->ld->wallet, current_height,

0 commit comments

Comments
 (0)