Skip to content

Commit c5b41a4

Browse files
committed
lightningd: wire up option_simple_close master-side handling
Adds the master-side glue for the `simpleclosed` subdaemon and removes the xfail markers from the integration tests: - simple_close_control.c: - starts the daemon with feerate bounds and shutdown scripts; - handles SIMPLECLOSED_GOT_SIG (broadcast closer tx), SIMPLECLOSED_CLOSEE_BROADCAST (broadcast closee tx), and SIMPLECLOSED_COMPLETE (advance state, resolve close RPC) - channel_control.c: route to peer_start_simpleclosed() instead of peer_start_closingd() when OPT_SIMPLE_CLOSE is negotiated; - peer_control.c: drop_to_chain_simple_close() sets up the funding-spend watch and resolves the close RPC without broadcasting the commitment tx, avoids it RBF-replacing the mutual close txs; - resend_closing_transactions() uses the same variant on restart Changelog-Experimental: Protocol: implement `option_simple_close` (BOLT2) for simpler one-shot mutual close fee negotiation. Enable with --dev-force-features=+60.
1 parent dca343f commit c5b41a4

7 files changed

Lines changed: 332 additions & 5 deletions

File tree

lightningd/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ lightning_gossip_compactd
88
lightning_hsmd
99
lightning_onchaind
1010
lightning_openingd
11+
lightning_simpleclosed
1112
lightning_websocketd

lightningd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ LIGHTNINGD_SRC := \
4242
lightningd/plugin_hook.c \
4343
lightningd/routehint.c \
4444
lightningd/runes.c \
45+
lightningd/simple_close_control.c \
4546
lightningd/subd.c \
4647
lightningd/wait.c \
4748
lightningd/watch.c

lightningd/channel_control.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include <ccan/cast/cast.h>
44
#include <ccan/tal/str/str.h>
55
#include <channeld/channeld_wiregen.h>
6+
#include <closingd/simpleclosed_wiregen.h>
67
#include <common/daemon.h>
8+
#include <common/features.h>
79
#include <common/json_command.h>
810
#include <common/psbt_open.h>
911
#include <common/shutdown_scriptpubkey.h>
@@ -22,6 +24,7 @@
2224
#include <lightningd/notification.h>
2325
#include <lightningd/peer_fd.h>
2426
#include <lightningd/peer_htlcs.h>
27+
#include <lightningd/simple_close_control.h>
2528
#include <unistd.h>
2629

2730
struct stfu_result
@@ -1387,6 +1390,7 @@ static void peer_start_closingd_after_shutdown(struct channel *channel,
13871390
const int *fds)
13881391
{
13891392
struct peer_fd *peer_fd;
1393+
struct lightningd *ld = channel->peer->ld;
13901394

13911395
if (!fromwire_channeld_shutdown_complete(msg)) {
13921396
channel_internal_error(channel, "bad shutdown_complete: %s",
@@ -1395,6 +1399,21 @@ static void peer_start_closingd_after_shutdown(struct channel *channel,
13951399
}
13961400
peer_fd = new_peer_fd_arr(msg, fds);
13971401

1402+
/* If both sides negotiated option_simple_close, use the simple close
1403+
* daemon instead of the legacy iterative fee negotiation daemon. */
1404+
if (feature_negotiated(ld->our_features,
1405+
channel->peer->their_features,
1406+
OPT_SIMPLE_CLOSE)) {
1407+
peer_start_simpleclosed(channel, peer_fd);
1408+
if (channel->state == CHANNELD_SHUTTING_DOWN)
1409+
channel_set_state(channel,
1410+
CHANNELD_SHUTTING_DOWN,
1411+
CLOSINGD_SIGEXCHANGE,
1412+
REASON_UNKNOWN,
1413+
"Start simpleclosed");
1414+
return;
1415+
}
1416+
13981417
/* This sets channel->owner, closes down channeld. */
13991418
peer_start_closingd(channel, peer_fd);
14001419

lightningd/peer_control.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <channeld/channeld_wiregen.h>
88
#include <common/addr.h>
99
#include <common/channel_id.h>
10+
#include <common/features.h>
1011
#include <common/htlc_trim.h>
1112
#include <common/initial_commit_tx.h>
1213
#include <common/json_channel_type.h>

lightningd/simple_close_control.c

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/* Master-side control for the simpleclosed subdaemon (option_simple_close). */
2+
#include "config.h"
3+
#include <bitcoin/script.h>
4+
#include <bitcoin/signature.h>
5+
#include <ccan/tal/str/str.h>
6+
#include <closingd/simpleclosed_wiregen.h>
7+
#include <common/fee_states.h>
8+
#include <common/shutdown_scriptpubkey.h>
9+
#include <errno.h>
10+
#include <hsmd/permissions.h>
11+
#include <inttypes.h>
12+
#include <lightningd/chaintopology.h>
13+
#include <lightningd/channel.h>
14+
#include <lightningd/channel_control.h>
15+
#include <lightningd/closing_control.h>
16+
#include <lightningd/connect_control.h>
17+
#include <lightningd/feerate.h>
18+
#include <lightningd/hsm_control.h>
19+
#include <lightningd/lightningd.h>
20+
#include <lightningd/peer_control.h>
21+
#include <lightningd/peer_fd.h>
22+
#include <lightningd/simple_close_control.h>
23+
#include <lightningd/subd.h>
24+
#include <wallet/wallet.h>
25+
#include <wally_bip32.h>
26+
27+
28+
/* Check that tx spends exactly our funding outpoint and every output goes
29+
* to a known shutdown script. Returns an error string, or NULL on success. */
30+
static const char *close_tx_check(const tal_t *ctx,
31+
const struct channel *channel,
32+
const struct bitcoin_tx *tx)
33+
{
34+
if (tx->wtx->num_inputs != 1)
35+
return tal_fmt(ctx, "expected 1 input, got %zu",
36+
tx->wtx->num_inputs);
37+
38+
if (!wally_tx_input_spends(&tx->wtx->inputs[0], &channel->funding))
39+
return tal_fmt(ctx, "does not spend funding outpoint %s",
40+
fmt_bitcoin_outpoint(ctx, &channel->funding));
41+
42+
for (size_t i = 0; i < tx->wtx->num_outputs; i++) {
43+
const struct wally_tx_output *out = &tx->wtx->outputs[i];
44+
/* Elements has an explicit fee output with no script. */
45+
if (out->script_len == 0) {
46+
if (chainparams->is_elements)
47+
continue;
48+
return tal_fmt(ctx, "output %zu has no script", i);
49+
}
50+
const u8 *script = tal_dup_arr(ctx, u8,
51+
out->script, out->script_len, 0);
52+
if (!scripteq(script, channel->shutdown_scriptpubkey[LOCAL])
53+
&& !scripteq(script, channel->shutdown_scriptpubkey[REMOTE]))
54+
return tal_fmt(ctx,
55+
"output %zu goes to unknown script %s",
56+
i, tal_hex(ctx, script));
57+
}
58+
return NULL;
59+
}
60+
61+
/* Master receives simpleclosed_got_sig: validate remote sig, store mutual
62+
* close tx, and reply with txid. drop_to_chain handles broadcast. */
63+
static void handle_simpleclosed_got_sig(struct channel *channel, const u8 *msg)
64+
{
65+
struct lightningd *ld = channel->peer->ld;
66+
struct bitcoin_tx *tx;
67+
struct bitcoin_txid txid;
68+
struct bitcoin_signature sig;
69+
const u8 *funding_wscript;
70+
71+
if (!fromwire_simpleclosed_got_sig(tmpctx, msg, &tx, &sig)) {
72+
channel_internal_error(channel, "bad simpleclosed_got_sig: %s",
73+
tal_hex(msg, msg));
74+
return;
75+
}
76+
tx->chainparams = chainparams;
77+
78+
const char *err = close_tx_check(tmpctx, channel, tx);
79+
if (err) {
80+
channel_internal_error(channel,
81+
"bad simpleclosed_got_sig: %s",
82+
err);
83+
return;
84+
}
85+
86+
funding_wscript = bitcoin_redeem_2of2(tmpctx,
87+
&channel->local_funding_pubkey,
88+
&channel->channel_info.remote_fundingkey);
89+
if (!check_tx_sig(tx, 0, NULL, funding_wscript,
90+
&channel->channel_info.remote_fundingkey, &sig)) {
91+
channel_internal_error(channel,
92+
"bad simpleclosed_got_sig: invalid sig: %s",
93+
tal_hex(msg, msg));
94+
return;
95+
}
96+
97+
channel_set_last_tx(channel, tx, &sig);
98+
wallet_channel_save(ld->wallet, channel);
99+
100+
bitcoin_txid(tx, &txid);
101+
log_info(channel->log,
102+
"Simple close: stored closer tx %s",
103+
fmt_bitcoin_txid(tmpctx, &txid));
104+
105+
subd_send_msg(channel->owner,
106+
take(towire_simpleclosed_got_sig_reply(NULL, &txid)));
107+
}
108+
109+
/* Master receives simpleclosed_closee_broadcast: validate remote sig and
110+
* store the mutual close tx. drop_to_chain handles broadcast. */
111+
static void handle_simpleclosed_closee_broadcast(struct channel *channel,
112+
const u8 *msg)
113+
{
114+
struct lightningd *ld = channel->peer->ld;
115+
struct bitcoin_tx *tx;
116+
struct bitcoin_txid txid;
117+
struct bitcoin_signature sig;
118+
const u8 *funding_wscript;
119+
120+
if (!fromwire_simpleclosed_closee_broadcast(tmpctx, msg, &tx, &sig)) {
121+
channel_internal_error(channel,
122+
"bad simpleclosed_closee_broadcast: %s",
123+
tal_hex(msg, msg));
124+
return;
125+
}
126+
tx->chainparams = chainparams;
127+
128+
const char *err = close_tx_check(tmpctx, channel, tx);
129+
if (err) {
130+
channel_internal_error(channel,
131+
"bad simpleclosed_closee_broadcast: %s",
132+
err);
133+
return;
134+
}
135+
136+
funding_wscript = bitcoin_redeem_2of2(tmpctx,
137+
&channel->local_funding_pubkey,
138+
&channel->channel_info.remote_fundingkey);
139+
if (!check_tx_sig(tx, 0, NULL, funding_wscript,
140+
&channel->channel_info.remote_fundingkey, &sig)) {
141+
channel_internal_error(channel,
142+
"bad simpleclosed_closee_broadcast: invalid sig: %s",
143+
tal_hex(msg, msg));
144+
return;
145+
}
146+
147+
channel_set_last_tx(channel, tx, &sig);
148+
wallet_channel_save(ld->wallet, channel);
149+
150+
bitcoin_txid(tx, &txid);
151+
log_info(channel->log,
152+
"Simple close: stored closee tx %s",
153+
fmt_bitcoin_txid(tmpctx, &txid));
154+
}
155+
156+
static void handle_simpleclosed_complete(struct channel *channel, const u8 *msg)
157+
{
158+
if (!fromwire_simpleclosed_complete(msg)) {
159+
channel_internal_error(channel,
160+
"bad simpleclosed_complete: %s",
161+
tal_hex(msg, msg));
162+
return;
163+
}
164+
165+
/* Don't report spurious failure when simpleclosed exits. */
166+
channel_set_owner(channel, NULL);
167+
channel_set_billboard(channel, false, NULL);
168+
169+
/* Retransmission only, ignore. */
170+
if (channel->state != CLOSINGD_SIGEXCHANGE)
171+
return;
172+
173+
channel_set_state(channel,
174+
CLOSINGD_SIGEXCHANGE,
175+
CLOSINGD_COMPLETE,
176+
REASON_UNKNOWN,
177+
"Simple close complete");
178+
179+
drop_to_chain(channel->peer->ld, channel, true, NULL);
180+
}
181+
182+
static unsigned int simpleclosed_msg(struct subd *sd, const u8 *msg,
183+
const int *fds UNUSED)
184+
{
185+
enum simpleclosed_wire t = fromwire_peektype(msg);
186+
187+
switch (t) {
188+
case WIRE_SIMPLECLOSED_GOT_SIG:
189+
handle_simpleclosed_got_sig(sd->channel, msg);
190+
return 0;
191+
case WIRE_SIMPLECLOSED_CLOSEE_BROADCAST:
192+
handle_simpleclosed_closee_broadcast(sd->channel, msg);
193+
return 0;
194+
case WIRE_SIMPLECLOSED_COMPLETE:
195+
handle_simpleclosed_complete(sd->channel, msg);
196+
return 0;
197+
198+
/* Inbound-only (master→daemon) — should not be received here. */
199+
case WIRE_SIMPLECLOSED_INIT:
200+
case WIRE_SIMPLECLOSED_GOT_SIG_REPLY:
201+
break;
202+
}
203+
204+
return 0;
205+
}
206+
207+
void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd)
208+
{
209+
u8 *initmsg;
210+
u32 feerate_perkw;
211+
struct amount_msat their_msat;
212+
int hsmfd;
213+
struct lightningd *ld = channel->peer->ld;
214+
u32 *local_wallet_index = NULL;
215+
struct ext_key *local_wallet_ext_key = NULL;
216+
u32 index_val;
217+
struct ext_key ext_key_val;
218+
219+
if (!channel->shutdown_scriptpubkey[REMOTE]) {
220+
channel_internal_error(channel,
221+
"Can't start simpleclosed: no remote script");
222+
return;
223+
}
224+
225+
hsmfd = hsm_get_client_fd(ld, &channel->peer->id, channel->dbid,
226+
HSM_PERM_SIGN_CLOSING_TX | HSM_PERM_COMMITMENT_POINT);
227+
if (hsmfd < 0) {
228+
log_broken(channel->log,
229+
"Could not get hsm fd for simpleclosed: %s",
230+
strerror(errno));
231+
force_peer_disconnect(ld, channel->peer,
232+
"Failed to get hsm fd for simpleclosed");
233+
return;
234+
}
235+
236+
channel_set_owner(channel,
237+
new_channel_subd(channel, ld, "lightning_simpleclosed", channel,
238+
&channel->peer->id, channel->log, true,
239+
simpleclosed_wire_name, simpleclosed_msg, channel_errmsg,
240+
channel_set_billboard, take(&peer_fd->fd), take(&hsmfd),
241+
NULL));
242+
243+
if (!channel->owner) {
244+
log_broken(channel->log,
245+
"Could not subdaemon simpleclosed: %s",
246+
strerror(errno));
247+
force_peer_disconnect(ld, channel->peer,
248+
"Failed to create simpleclosed");
249+
return;
250+
}
251+
252+
/* Compute their balance. */
253+
if (!amount_sat_sub_msat(&their_msat,
254+
channel->funding_sats, channel->our_msat)) {
255+
log_broken(channel->log,
256+
"our_msat overflow on simple close: %s minus %s",
257+
fmt_amount_sat(tmpctx, channel->funding_sats),
258+
fmt_amount_msat(tmpctx, channel->our_msat));
259+
channel_fail_permanent(channel, REASON_LOCAL,
260+
"our_msat overflow on simple close");
261+
return;
262+
}
263+
264+
feerate_perkw = mutual_close_feerate(ld->topology);
265+
if (!feerate_perkw) {
266+
feerate_perkw = get_feerate(channel->fee_states,
267+
channel->opener, LOCAL) / 2;
268+
if (feerate_perkw < get_feerate_floor(ld->topology))
269+
feerate_perkw = get_feerate_floor(ld->topology);
270+
}
271+
272+
/* Wallet key for our output. */
273+
if (wallet_can_spend(ld->wallet,
274+
channel->shutdown_scriptpubkey[LOCAL],
275+
tal_bytelen(channel->shutdown_scriptpubkey[LOCAL]),
276+
&index_val, NULL)) {
277+
if (bip32_key_from_parent(ld->bip32_base, index_val,
278+
BIP32_FLAG_KEY_PUBLIC,
279+
&ext_key_val) != WALLY_OK) {
280+
channel_internal_error(channel,
281+
"Could not derive ext public key");
282+
return;
283+
}
284+
local_wallet_index = &index_val;
285+
local_wallet_ext_key = &ext_key_val;
286+
}
287+
288+
initmsg = towire_simpleclosed_init(tmpctx, chainparams, &channel->cid,
289+
&channel->funding, channel->funding_sats,
290+
&channel->local_funding_pubkey,
291+
&channel->channel_info.remote_fundingkey,
292+
amount_msat_to_sat_round_down(channel->our_msat),
293+
amount_msat_to_sat_round_down(their_msat),
294+
channel->our_config.dust_limit, feerate_perkw, local_wallet_index,
295+
local_wallet_ext_key, channel->shutdown_scriptpubkey[LOCAL],
296+
channel->shutdown_scriptpubkey[REMOTE], channel->opener);
297+
298+
subd_send_msg(channel->owner, take(initmsg));
299+
}

lightningd/simple_close_control.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H
2+
#define LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H
3+
#include "config.h"
4+
5+
struct channel;
6+
struct peer_fd;
7+
8+
/* Start the simpleclosed subdaemon for option_simple_close negotiation. */
9+
void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd);
10+
11+
#endif /* LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H */

0 commit comments

Comments
 (0)