From feb6a240b32809c555f169bbff126d73833cfc15 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 13 Aug 2026 20:06:08 +0930 Subject: [PATCH 1/5] general: run `make update-mocks`. And fix prototypes which caused mocks to break. Signed-off-by: Rusty Russell --- common/test/run-htable.c | 9 --------- common/test/run-route-infloop.c | 9 --------- lightningd/subd.h | 4 ++-- lightningd/test/run-close_tx_check.c | 2 +- lightningd/test/run-invoice-select-inchan.c | 10 ---------- onchaind/test/run-grind_feerate-bug.c | 6 ------ wallet/test/run-chain_moves_duplicate-detect.c | 5 ----- wallet/test/run-db.c | 5 ----- .../run-migrate_remove_chain_moves_duplicates.c | 5 ----- wallet/test/run-wallet.c | 15 --------------- 10 files changed, 3 insertions(+), 67 deletions(-) diff --git a/common/test/run-htable.c b/common/test/run-htable.c index 32a2631c37af..267ad8c03f10 100644 --- a/common/test/run-htable.c +++ b/common/test/run-htable.c @@ -81,15 +81,6 @@ u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) /* Generated stub for fromwire_u8_array */ void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) { fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } -/* Generated stub for memleak_add_helper_ */ -void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, - const tal_t *)){ } -/* Generated stub for memleak_scan_htable */ -void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED) -{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); } -/* Generated stub for notleak_ */ -void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) -{ fprintf(stderr, "notleak_ called!\n"); abort(); } /* Generated stub for towire */ void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) { fprintf(stderr, "towire called!\n"); abort(); } diff --git a/common/test/run-route-infloop.c b/common/test/run-route-infloop.c index 4e4ee302ff4a..7556fd2f93af 100644 --- a/common/test/run-route-infloop.c +++ b/common/test/run-route-infloop.c @@ -32,15 +32,6 @@ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *record UNNEEDED, struct tlv_field **fields UNNEEDED, const u64 *extra_types UNNEEDED, size_t *err_off UNNEEDED, u64 *err_type UNNEEDED) { fprintf(stderr, "fromwire_tlv called!\n"); abort(); } -/* Generated stub for memleak_add_helper_ */ -void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, - const tal_t *)){ } -/* Generated stub for memleak_scan_htable */ -void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED) -{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); } -/* Generated stub for notleak_ */ -void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) -{ fprintf(stderr, "notleak_ called!\n"); abort(); } /* Generated stub for sciddir_or_pubkey_from_node_id */ bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, const struct node_id *node_id UNNEEDED) diff --git a/lightningd/subd.h b/lightningd/subd.h index 8518b065590a..3ad73bbba8f4 100644 --- a/lightningd/subd.h +++ b/lightningd/subd.h @@ -126,13 +126,13 @@ struct subd *new_channel_subd_(const tal_t *ctx, const char *(*msgname)(int msgtype), unsigned int (*msgcb)(struct subd *, const u8 *, const int *fds), - void (*errcb)(void *channel, + void (*errcb)(void *errcb_channel, struct peer_fd *peer_fd, const char *desc, const u8 *err_for_them, bool disconnect, bool warning), - void (*billboardcb)(void *channel, bool perm, + void (*billboardcb)(void *bollboardcb_channel, bool perm, const char *happenings), ...); diff --git a/lightningd/test/run-close_tx_check.c b/lightningd/test/run-close_tx_check.c index 9b132647bc64..de71295078cc 100644 --- a/lightningd/test/run-close_tx_check.c +++ b/lightningd/test/run-close_tx_check.c @@ -100,7 +100,7 @@ struct subd *new_channel_subd_(const tal_t *ctx UNNEEDED, const u8 *err_for_them UNNEEDED, bool disconnect UNNEEDED, bool warning) UNNEEDED, - void (*billboardcb)(void *billboardcb_channel UNNEEDED, bool perm UNNEEDED, + void (*billboardcb)(void *bollboardcb_channel UNNEEDED, bool perm UNNEEDED, const char *happenings) UNNEEDED, ...) { fprintf(stderr, "new_channel_subd_ called!\n"); abort(); } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 0b42cb1c7178..d41bf5466e62 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -257,16 +257,6 @@ struct channel *find_channel_by_id(const struct peer *peer UNNEEDED, struct plugin *find_plugin_for_command(struct lightningd *ld UNNEEDED, const char *cmd_name UNNEEDED) { fprintf(stderr, "find_plugin_for_command called!\n"); abort(); } -/* Generated stub for find_txwatch_ */ -struct txwatch *find_txwatch_(struct chain_topology *topo UNNEEDED, - const struct bitcoin_txid *txid UNNEEDED, - enum watch_result (*cb)(struct lightningd *ld UNNEEDED, - const struct bitcoin_txid * UNNEEDED, - const struct bitcoin_tx * UNNEEDED, - unsigned int depth UNNEEDED, - void *arg) UNNEEDED, - void *arg UNNEEDED) -{ fprintf(stderr, "find_txwatch_ called!\n"); abort(); } /* Generated stub for fixup_htlcs_out */ void fixup_htlcs_out(struct lightningd *ld UNNEEDED) { fprintf(stderr, "fixup_htlcs_out called!\n"); abort(); } diff --git a/onchaind/test/run-grind_feerate-bug.c b/onchaind/test/run-grind_feerate-bug.c index a389a499038a..485490201537 100644 --- a/onchaind/test/run-grind_feerate-bug.c +++ b/onchaind/test/run-grind_feerate-bug.c @@ -88,15 +88,9 @@ struct bitcoin_tx *htlc_success_tx(const tal_t *ctx UNNEEDED, /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } -/* Generated stub for memleak_add_helper_ */ -void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, - const tal_t *)){ } /* Generated stub for memleak_ptr */ bool memleak_ptr(struct htable *memtable UNNEEDED, const void *p UNNEEDED) { fprintf(stderr, "memleak_ptr called!\n"); abort(); } -/* Generated stub for memleak_scan_htable */ -void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED) -{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); } /* Generated stub for memleak_scan_obj */ void memleak_scan_obj(struct htable *memtable UNNEEDED, const void *obj UNNEEDED) { fprintf(stderr, "memleak_scan_obj called!\n"); abort(); } diff --git a/wallet/test/run-chain_moves_duplicate-detect.c b/wallet/test/run-chain_moves_duplicate-detect.c index cc2797950bd3..0bfb150682de 100644 --- a/wallet/test/run-chain_moves_duplicate-detect.c +++ b/wallet/test/run-chain_moves_duplicate-detect.c @@ -377,11 +377,6 @@ const char *wait_index_name(enum wait_index index UNNEEDED) /* Generated stub for wait_subsystem_name */ const char *wait_subsystem_name(enum wait_subsystem subsystem UNNEEDED) { fprintf(stderr, "wait_subsystem_name called!\n"); abort(); } -/* Generated stub for watchman_unwatch_outpoint */ -void watchman_unwatch_outpoint(struct lightningd *ld UNNEEDED, - const char *owner UNNEEDED, - const struct bitcoin_outpoint *outpoint UNNEEDED) -{ fprintf(stderr, "watchman_unwatch_outpoint called!\n"); abort(); } /* Generated stub for watchman_watch_outpoint */ void watchman_watch_outpoint(struct lightningd *ld UNNEEDED, const char *owner UNNEEDED, diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c index 801839c678bb..8494e7df11ea 100644 --- a/wallet/test/run-db.c +++ b/wallet/test/run-db.c @@ -390,11 +390,6 @@ const char *wait_index_name(enum wait_index index UNNEEDED) /* Generated stub for wait_subsystem_name */ const char *wait_subsystem_name(enum wait_subsystem subsystem UNNEEDED) { fprintf(stderr, "wait_subsystem_name called!\n"); abort(); } -/* Generated stub for watchman_unwatch_outpoint */ -void watchman_unwatch_outpoint(struct lightningd *ld UNNEEDED, - const char *owner UNNEEDED, - const struct bitcoin_outpoint *outpoint UNNEEDED) -{ fprintf(stderr, "watchman_unwatch_outpoint called!\n"); abort(); } /* Generated stub for watchman_watch_outpoint */ void watchman_watch_outpoint(struct lightningd *ld UNNEEDED, const char *owner UNNEEDED, diff --git a/wallet/test/run-migrate_remove_chain_moves_duplicates.c b/wallet/test/run-migrate_remove_chain_moves_duplicates.c index ad0550a6be5f..d9478ad8e310 100644 --- a/wallet/test/run-migrate_remove_chain_moves_duplicates.c +++ b/wallet/test/run-migrate_remove_chain_moves_duplicates.c @@ -426,11 +426,6 @@ const char *wait_index_name(enum wait_index index UNNEEDED) /* Generated stub for wait_subsystem_name */ const char *wait_subsystem_name(enum wait_subsystem subsystem UNNEEDED) { fprintf(stderr, "wait_subsystem_name called!\n"); abort(); } -/* Generated stub for watchman_unwatch_outpoint */ -void watchman_unwatch_outpoint(struct lightningd *ld UNNEEDED, - const char *owner UNNEEDED, - const struct bitcoin_outpoint *outpoint UNNEEDED) -{ fprintf(stderr, "watchman_unwatch_outpoint called!\n"); abort(); } /* Generated stub for watchman_watch_outpoint */ void watchman_watch_outpoint(struct lightningd *ld UNNEEDED, const char *owner UNNEEDED, diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index c44307710943..0098e96af566 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -256,16 +256,6 @@ void fatal(const char *fmt UNNEEDED, ...) /* Generated stub for fatal_vfmt */ void fatal_vfmt(const char *fmt UNNEEDED, va_list ap UNNEEDED) { fprintf(stderr, "fatal_vfmt called!\n"); abort(); } -/* Generated stub for find_txwatch_ */ -struct txwatch *find_txwatch_(struct chain_topology *topo UNNEEDED, - const struct bitcoin_txid *txid UNNEEDED, - enum watch_result (*cb)(struct lightningd *ld UNNEEDED, - const struct bitcoin_txid * UNNEEDED, - const struct bitcoin_tx * UNNEEDED, - unsigned int depth UNNEEDED, - void *arg) UNNEEDED, - void *arg UNNEEDED) -{ fprintf(stderr, "find_txwatch_ called!\n"); abort(); } /* Generated stub for force_peer_disconnect */ void force_peer_disconnect(struct lightningd *ld UNNEEDED, const struct peer *peer UNNEEDED, @@ -817,11 +807,6 @@ struct txowatch *watch_txo(const tal_t *ctx UNNEEDED, size_t input_num UNNEEDED, const struct block *block)) { fprintf(stderr, "watch_txo called!\n"); abort(); } -/* Generated stub for watchman_unwatch_outpoint */ -void watchman_unwatch_outpoint(struct lightningd *ld UNNEEDED, - const char *owner UNNEEDED, - const struct bitcoin_outpoint *outpoint UNNEEDED) -{ fprintf(stderr, "watchman_unwatch_outpoint called!\n"); abort(); } /* Generated stub for watchman_watch_outpoint */ void watchman_watch_outpoint(struct lightningd *ld UNNEEDED, const char *owner UNNEEDED, From 81e2ae7afdc872ae794bbee004fd7d41d55c11ed Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 14 Aug 2026 11:39:18 +0930 Subject: [PATCH 2/5] common: expose SIMPLE_CLOSE_WEIGHT in header. We're going to need it if lightningd wants to double-check the tx it gets from simpleclosed. Signed-off-by: Rusty Russell --- closingd/simpleclosed.c | 6 +----- common/Makefile | 3 ++- common/simple_close_weight.h | 12 ++++++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 common/simple_close_weight.h diff --git a/closingd/simpleclosed.c b/closingd/simpleclosed.c index ab1e8dccc9cc..cef5c7c48056 100644 --- a/closingd/simpleclosed.c +++ b/closingd/simpleclosed.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -34,11 +35,6 @@ #define PEER_FD 3 #define HSM_FD 4 -/* Approx weight of a simple-close tx with both outputs (vbytes * 4 for weight). - * Input: 41vb, witness: ~222wu/4=55.5vb, outputs: ~65vb each, overhead: 11vb - * Total ~236 vbytes = ~704 weight + witness ~222 = ~926wu, round to 900. */ -#define SIMPLE_CLOSE_WEIGHT 900 - static const u8 *hsm_req(const tal_t *ctx, const u8 *req TAKES) { u8 *msg; diff --git a/common/Makefile b/common/Makefile index d60606420807..4828c228ed12 100644 --- a/common/Makefile +++ b/common/Makefile @@ -125,7 +125,8 @@ COMMON_HEADERS_NOGEN := $(COMMON_SRC_NOGEN:.c=.h) \ common/hsm_version.h \ common/htlc.h \ common/jsonrpc_errors.h \ - common/overflows.h + common/overflows.h \ + common/simple_close_weight.h COMMON_HEADERS_GEN := common/htlc_state_names_gen.h common/status_wiregen.h common/peer_status_wiregen.h common/scb_wiregen.h common/gossip_store_wiregen.h diff --git a/common/simple_close_weight.h b/common/simple_close_weight.h new file mode 100644 index 000000000000..2f96d701a960 --- /dev/null +++ b/common/simple_close_weight.h @@ -0,0 +1,12 @@ +#ifndef LIGHTNING_COMMON_SIMPLE_CLOSE_WEIGHT_H +#define LIGHTNING_COMMON_SIMPLE_CLOSE_WEIGHT_H +#include "config.h" +/* When lightningd checks the closing tx, it must use the same weight + * approximation as simpleclosed, otherwise it might reject it, so we share + * this constant. */ + +/* Approx weight of a simple-close tx with both outputs (vbytes * 4 for weight). + * Input: 41vb, witness: ~222wu/4=55.5vb, outputs: ~65vb each, overhead: 11vb + * Total ~236 vbytes = ~704 weight + witness ~222 = ~926wu, round to 900. */ +#define SIMPLE_CLOSE_WEIGHT 900 +#endif /* LIGHTNING_COMMON_SIMPLE_CLOSE_WEIGHT_H */ From 23970192a093c8b29d231de587fce3c8d8c2cef7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 14 Aug 2026 11:39:32 +0930 Subject: [PATCH 3/5] closingd: rename wire functions. From lightningd's perspective, there are two messages: 1. We got a signature on our closing tx. 2. We got a signature on their closing tx. The closer/closee naming is confusing. Signed-off-by: Rusty Russell --- closingd/simpleclosed.c | 14 +++++++------- closingd/simpleclosed_wire.csv | 16 ++++++++-------- lightningd/simple_close_control.c | 28 ++++++++++++++-------------- lightningd/test/run-close_tx_check.c | 18 +++++++++--------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/closingd/simpleclosed.c b/closingd/simpleclosed.c index cef5c7c48056..f504504b153d 100644 --- a/closingd/simpleclosed.c +++ b/closingd/simpleclosed.c @@ -53,13 +53,13 @@ static const u8 *hsm_req(const tal_t *ctx, const u8 *req TAKES) static struct per_peer_state *pps; /* Tell master we got peer's closing_sig for our tx; block for txid reply. */ -static struct bitcoin_txid master_got_sig(struct bitcoin_tx *tx, - const struct bitcoin_signature *sig) +static struct bitcoin_txid master_got_sig_ourtx(const struct bitcoin_tx *tx, + const struct bitcoin_signature *sig) { struct bitcoin_txid txid; u8 *msg; - msg = towire_simpleclosed_got_sig(tmpctx, tx, sig); + msg = towire_simpleclosed_our_closing_tx(tmpctx, tx, sig); if (!wire_sync_write(REQ_FD, take(msg))) status_failed(STATUS_FAIL_MASTER_IO, "Writing got_sig: %s", @@ -70,7 +70,7 @@ static struct bitcoin_txid master_got_sig(struct bitcoin_tx *tx, status_failed(STATUS_FAIL_MASTER_IO, "Reading got_sig_reply: %s", strerror(errno)); - if (!fromwire_simpleclosed_got_sig_reply(msg, &txid)) + if (!fromwire_simpleclosed_our_closing_tx_reply(msg, &txid)) status_failed(STATUS_FAIL_MASTER_IO, "Bad got_sig_reply: %s", tal_hex(tmpctx, msg)); @@ -458,8 +458,8 @@ static struct bitcoin_tx *handle_closing_complete( /* Tell master to validate, store, and handle broadcast. */ wire_sync_write(REQ_FD, - take(towire_simpleclosed_closee_broadcast(NULL, - chosen_tx, &their_sig))); + take(towire_simpleclosed_their_closing_tx(NULL, + chosen_tx, &their_sig))); return chosen_tx; } @@ -682,7 +682,7 @@ int main(int argc, char *argv[]) local_wallet_index, local_wallet_ext_key, local_sat, remote_sat, dust_limit, sent_closer_script, sent_closee_script, sent_fee, sent_locktime, sent_tlvs, msg, &their_sig); - txid = master_got_sig(closing_tx, &their_sig); + txid = master_got_sig_ourtx(closing_tx, &their_sig); status_debug("Closer tx stored by master: %s", fmt_bitcoin_txid(tmpctx, &txid)); got_our_sig = true; diff --git a/closingd/simpleclosed_wire.csv b/closingd/simpleclosed_wire.csv index d3108e3a9ba0..3aadc7a4f42e 100644 --- a/closingd/simpleclosed_wire.csv +++ b/closingd/simpleclosed_wire.csv @@ -31,19 +31,19 @@ msgdata,simpleclosed_init,opener,enum side, # simpleclosed tells master it got a valid closing_sig for our closing_complete; # master should broadcast this tx. -msgtype,simpleclosed_got_sig,3002 -msgdata,simpleclosed_got_sig,tx,bitcoin_tx, -msgdata,simpleclosed_got_sig,sig,bitcoin_signature, +msgtype,simpleclosed_our_closing_tx,3002 +msgdata,simpleclosed_our_closing_tx,tx,bitcoin_tx, +msgdata,simpleclosed_our_closing_tx,their_sig,bitcoin_signature, # Master replies with the txid (after storing/broadcasting). -msgtype,simpleclosed_got_sig_reply,3102 -msgdata,simpleclosed_got_sig_reply,closing_txid,bitcoin_txid, +msgtype,simpleclosed_our_closing_tx_reply,3102 +msgdata,simpleclosed_our_closing_tx_reply,closing_txid,bitcoin_txid, # simpleclosed tells master it signed the peer's closing tx (as the closee); # master validates and stores, drop_to_chain handles broadcast. -msgtype,simpleclosed_closee_broadcast,3003 -msgdata,simpleclosed_closee_broadcast,tx,bitcoin_tx, -msgdata,simpleclosed_closee_broadcast,sig,bitcoin_signature, +msgtype,simpleclosed_their_closing_tx,3003 +msgdata,simpleclosed_their_closing_tx,tx,bitcoin_tx, +msgdata,simpleclosed_their_closing_tx,their_sig,bitcoin_signature, # Negotiations complete, exiting. msgtype,simpleclosed_complete,3004 diff --git a/lightningd/simple_close_control.c b/lightningd/simple_close_control.c index 07e08473e630..9007d1430dcc 100644 --- a/lightningd/simple_close_control.c +++ b/lightningd/simple_close_control.c @@ -92,7 +92,7 @@ static const char *close_tx_check(const tal_t *ctx, /* Master receives simpleclosed_got_sig: validate remote sig, store mutual * close tx, and reply with txid. drop_to_chain handles broadcast. */ -static void handle_simpleclosed_got_sig(struct channel *channel, const u8 *msg) +static void handle_simpleclosed_our_closing_tx(struct channel *channel, const u8 *msg) { struct lightningd *ld = channel->peer->ld; struct bitcoin_tx *tx; @@ -100,7 +100,7 @@ static void handle_simpleclosed_got_sig(struct channel *channel, const u8 *msg) struct bitcoin_signature sig; const u8 *funding_wscript; - if (!fromwire_simpleclosed_got_sig(tmpctx, msg, &tx, &sig)) { + if (!fromwire_simpleclosed_our_closing_tx(tmpctx, msg, &tx, &sig)) { channel_internal_error(channel, "bad simpleclosed_got_sig: %s", tal_hex(msg, msg)); return; @@ -135,12 +135,12 @@ static void handle_simpleclosed_got_sig(struct channel *channel, const u8 *msg) fmt_bitcoin_txid(tmpctx, &txid)); subd_send_msg(channel->owner, - take(towire_simpleclosed_got_sig_reply(NULL, &txid))); + take(towire_simpleclosed_our_closing_tx_reply(NULL, &txid))); } -/* Master receives simpleclosed_closee_broadcast: validate remote sig and +/* Master receives simpleclosed_their_closing_tx: validate remote sig and * store the mutual close tx. drop_to_chain handles broadcast. */ -static void handle_simpleclosed_closee_broadcast(struct channel *channel, +static void handle_simpleclosed_their_closing_tx(struct channel *channel, const u8 *msg) { struct lightningd *ld = channel->peer->ld; @@ -149,9 +149,9 @@ static void handle_simpleclosed_closee_broadcast(struct channel *channel, struct bitcoin_signature sig; const u8 *funding_wscript; - if (!fromwire_simpleclosed_closee_broadcast(tmpctx, msg, &tx, &sig)) { + if (!fromwire_simpleclosed_their_closing_tx(tmpctx, msg, &tx, &sig)) { channel_internal_error(channel, - "bad simpleclosed_closee_broadcast: %s", + "bad simpleclosed_their_closing_tx: %s", tal_hex(msg, msg)); return; } @@ -160,7 +160,7 @@ static void handle_simpleclosed_closee_broadcast(struct channel *channel, const char *err = close_tx_check(tmpctx, channel, tx); if (err) { channel_internal_error(channel, - "bad simpleclosed_closee_broadcast: %s", + "bad simpleclosed_their_closing_tx: %s", err); return; } @@ -171,7 +171,7 @@ static void handle_simpleclosed_closee_broadcast(struct channel *channel, if (!check_tx_sig(tx, 0, NULL, funding_wscript, &channel->channel_info.remote_fundingkey, &sig)) { channel_internal_error(channel, - "bad simpleclosed_closee_broadcast: invalid sig: %s", + "bad simpleclosed_their_closing_tx: invalid sig: %s", tal_hex(msg, msg)); return; } @@ -242,11 +242,11 @@ static unsigned int simpleclosed_msg(struct subd *sd, const u8 *msg, enum simpleclosed_wire t = fromwire_peektype(msg); switch (t) { - case WIRE_SIMPLECLOSED_GOT_SIG: - handle_simpleclosed_got_sig(sd->channel, msg); + case WIRE_SIMPLECLOSED_OUR_CLOSING_TX: + handle_simpleclosed_our_closing_tx(sd->channel, msg); return 0; - case WIRE_SIMPLECLOSED_CLOSEE_BROADCAST: - handle_simpleclosed_closee_broadcast(sd->channel, msg); + case WIRE_SIMPLECLOSED_THEIR_CLOSING_TX: + handle_simpleclosed_their_closing_tx(sd->channel, msg); return 0; case WIRE_SIMPLECLOSED_COMPLETE: handle_simpleclosed_complete(sd->channel, msg); @@ -254,7 +254,7 @@ static unsigned int simpleclosed_msg(struct subd *sd, const u8 *msg, /* Inbound-only (master→daemon) — should not be received here. */ case WIRE_SIMPLECLOSED_INIT: - case WIRE_SIMPLECLOSED_GOT_SIG_REPLY: + case WIRE_SIMPLECLOSED_OUR_CLOSING_TX_REPLY: break; } diff --git a/lightningd/test/run-close_tx_check.c b/lightningd/test/run-close_tx_check.c index de71295078cc..6edad6d08f7f 100644 --- a/lightningd/test/run-close_tx_check.c +++ b/lightningd/test/run-close_tx_check.c @@ -55,15 +55,15 @@ void force_peer_disconnect(struct lightningd *ld UNNEEDED, const struct peer *peer UNNEEDED, const char *why UNNEEDED) { fprintf(stderr, "force_peer_disconnect called!\n"); abort(); } -/* Generated stub for fromwire_simpleclosed_closee_broadcast */ -bool fromwire_simpleclosed_closee_broadcast(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct bitcoin_tx **tx UNNEEDED, struct bitcoin_signature *sig UNNEEDED) -{ fprintf(stderr, "fromwire_simpleclosed_closee_broadcast called!\n"); abort(); } /* Generated stub for fromwire_simpleclosed_complete */ bool fromwire_simpleclosed_complete(const void *p UNNEEDED, bool *delay_broadcast UNNEEDED) { fprintf(stderr, "fromwire_simpleclosed_complete called!\n"); abort(); } -/* Generated stub for fromwire_simpleclosed_got_sig */ -bool fromwire_simpleclosed_got_sig(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct bitcoin_tx **tx UNNEEDED, struct bitcoin_signature *sig UNNEEDED) -{ fprintf(stderr, "fromwire_simpleclosed_got_sig called!\n"); abort(); } +/* Generated stub for fromwire_simpleclosed_our_closing_tx */ +bool fromwire_simpleclosed_our_closing_tx(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct bitcoin_tx **tx UNNEEDED, struct bitcoin_signature *their_sig UNNEEDED) +{ fprintf(stderr, "fromwire_simpleclosed_our_closing_tx called!\n"); abort(); } +/* Generated stub for fromwire_simpleclosed_their_closing_tx */ +bool fromwire_simpleclosed_their_closing_tx(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct bitcoin_tx **tx UNNEEDED, struct bitcoin_signature *their_sig UNNEEDED) +{ fprintf(stderr, "fromwire_simpleclosed_their_closing_tx called!\n"); abort(); } /* Generated stub for get_feerate_floor */ u32 get_feerate_floor(const struct chain_topology *topo UNNEEDED) { fprintf(stderr, "get_feerate_floor called!\n"); abort(); } @@ -114,12 +114,12 @@ const char *simpleclosed_wire_name(int e UNNEEDED) /* Generated stub for subd_send_msg */ void subd_send_msg(struct subd *sd UNNEEDED, const u8 *msg_out UNNEEDED) { fprintf(stderr, "subd_send_msg called!\n"); abort(); } -/* Generated stub for towire_simpleclosed_got_sig_reply */ -u8 *towire_simpleclosed_got_sig_reply(const tal_t *ctx UNNEEDED, const struct bitcoin_txid *closing_txid UNNEEDED) -{ fprintf(stderr, "towire_simpleclosed_got_sig_reply called!\n"); abort(); } /* Generated stub for towire_simpleclosed_init */ u8 *towire_simpleclosed_init(const tal_t *ctx UNNEEDED, const struct chainparams *chainparams UNNEEDED, const struct channel_id *channel_id UNNEEDED, const struct bitcoin_outpoint *funding UNNEEDED, struct amount_sat funding_satoshi UNNEEDED, const struct pubkey *local_fundingkey UNNEEDED, const struct pubkey *remote_fundingkey UNNEEDED, struct amount_sat local_sat UNNEEDED, struct amount_sat remote_sat UNNEEDED, struct amount_sat our_dust_limit UNNEEDED, u32 feerate_perkw UNNEEDED, u32 *local_wallet_index UNNEEDED, const struct ext_key *local_wallet_ext_key UNNEEDED, const u8 *local_scriptpubkey UNNEEDED, const u8 *remote_scriptpubkey UNNEEDED, enum side opener UNNEEDED) { fprintf(stderr, "towire_simpleclosed_init called!\n"); abort(); } +/* Generated stub for towire_simpleclosed_our_closing_tx_reply */ +u8 *towire_simpleclosed_our_closing_tx_reply(const tal_t *ctx UNNEEDED, const struct bitcoin_txid *closing_txid UNNEEDED) +{ fprintf(stderr, "towire_simpleclosed_our_closing_tx_reply called!\n"); abort(); } /* Generated stub for wallet_can_spend */ bool wallet_can_spend(struct wallet *w UNNEEDED, const u8 *script UNNEEDED, From 62452ac53d9e258f9d57ed90bd7cb5b95558a0d1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 14 Aug 2026 11:39:32 +0930 Subject: [PATCH 4/5] lightningd: double-check closing tx actually pays correctly to us. simpleclosed checks it, but for thoroughness (and to prevent bugs and avoid any potential exploits in it) we need to check it too. Signed-off-by: Rusty Russell --- lightningd/simple_close_control.c | 68 ++++++++++++++++++++++------ lightningd/test/run-close_tx_check.c | 64 +++++++++++++++++++------- 2 files changed, 100 insertions(+), 32 deletions(-) diff --git a/lightningd/simple_close_control.c b/lightningd/simple_close_control.c index 9007d1430dcc..968d4bb19988 100644 --- a/lightningd/simple_close_control.c +++ b/lightningd/simple_close_control.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -35,9 +36,13 @@ /* Check that tx spends exactly our funding outpoint and every output goes * to a known shutdown script. Returns an error string, or NULL on success. */ static const char *close_tx_check(const tal_t *ctx, - const struct channel *channel, - const struct bitcoin_tx *tx) + const struct channel *channel, + const struct bitcoin_tx *tx, + u32 max_feerate_wepay) { + bool our_output_exists = false, our_output_ok = false; + struct amount_sat expected_amt_tous; + if (tx->wtx->num_inputs != 1) return tal_fmt(ctx, "expected 1 input, got %zu", tx->wtx->num_inputs); @@ -46,6 +51,13 @@ static const char *close_tx_check(const tal_t *ctx, return tal_fmt(ctx, "does not spend funding outpoint %s", fmt_bitcoin_outpoint(ctx, &channel->funding)); + /* Lowest amount we expect: must match simpleclosed's heuristic */ + expected_amt_tous = amount_msat_to_sat_round_down(channel->our_msat); + if (!amount_sat_sub(&expected_amt_tous, + expected_amt_tous, + amount_sat((u64)max_feerate_wepay * SIMPLE_CLOSE_WEIGHT / 1000))) + expected_amt_tous = AMOUNT_SAT(0); + for (size_t i = 0; i < tx->wtx->num_outputs; i++) { const struct wally_tx_output *out = &tx->wtx->outputs[i]; /* Elements has an explicit fee output with no script. */ @@ -56,8 +68,15 @@ static const char *close_tx_check(const tal_t *ctx, } const u8 *script = tal_dup_arr(ctx, u8, out->script, out->script_len, 0); - if (scripteq(script, channel->shutdown_scriptpubkey[LOCAL])) + + /* In case they match our output, we check if *any* output to us is good */ + if (scripteq(script, channel->shutdown_scriptpubkey[LOCAL])) { + our_output_exists = true; + if (amount_sat_greater_eq(bitcoin_tx_output_get_amount_sat(tx, i), + expected_amt_tous)) + our_output_ok = true; continue; + } if (scripteq(script, channel->shutdown_scriptpubkey[REMOTE])) continue; /* Our own output is always paid to shutdown_scriptpubkey[LOCAL] @@ -87,9 +106,35 @@ static const char *close_tx_check(const tal_t *ctx, "output %zu goes to unknown script %s", i, tal_hex(ctx, script)); } + + /* If we expected an output, make sure it was ok. */ + if (amount_sat_greater_eq(expected_amt_tous, + channel->our_config.dust_limit)) { + if (!our_output_exists) + return tal_fmt(ctx, + "No output pays to us, and we expect at least %s", + fmt_amount_sat(tmpctx, expected_amt_tous)); + if (!our_output_ok) + return tal_fmt(ctx, + "Output to us doesn't pay enough (we expected at least %s)", + fmt_amount_sat(tmpctx, expected_amt_tous)); + } return NULL; } +static u32 feerate_for_close(const struct channel *channel) +{ + struct lightningd *ld = channel->peer->ld; + u32 feerate_perkw = mutual_close_feerate(ld->topology); + if (!feerate_perkw) { + feerate_perkw = get_feerate(channel->fee_states, + channel->opener, LOCAL) / 2; + if (feerate_perkw < get_feerate_floor(ld->topology)) + feerate_perkw = get_feerate_floor(ld->topology); + } + return feerate_perkw; +} + /* Master receives simpleclosed_got_sig: validate remote sig, store mutual * close tx, and reply with txid. drop_to_chain handles broadcast. */ static void handle_simpleclosed_our_closing_tx(struct channel *channel, const u8 *msg) @@ -107,7 +152,8 @@ static void handle_simpleclosed_our_closing_tx(struct channel *channel, const u8 } tx->chainparams = chainparams; - const char *err = close_tx_check(tmpctx, channel, tx); + const char *err = close_tx_check(tmpctx, channel, tx, + feerate_for_close(channel)); if (err) { channel_internal_error(channel, "bad simpleclosed_got_sig: %s", @@ -157,7 +203,8 @@ static void handle_simpleclosed_their_closing_tx(struct channel *channel, } tx->chainparams = chainparams; - const char *err = close_tx_check(tmpctx, channel, tx); + /* Their tx, we don't pay any fee */ + const char *err = close_tx_check(tmpctx, channel, tx, 0); if (err) { channel_internal_error(channel, "bad simpleclosed_their_closing_tx: %s", @@ -264,7 +311,6 @@ static unsigned int simpleclosed_msg(struct subd *sd, const u8 *msg, void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd) { u8 *initmsg; - u32 feerate_perkw; struct amount_msat their_msat; int hsmfd; struct lightningd *ld = channel->peer->ld; @@ -318,14 +364,6 @@ void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd) return; } - feerate_perkw = mutual_close_feerate(ld->topology); - if (!feerate_perkw) { - feerate_perkw = get_feerate(channel->fee_states, - channel->opener, LOCAL) / 2; - if (feerate_perkw < get_feerate_floor(ld->topology)) - feerate_perkw = get_feerate_floor(ld->topology); - } - /* Wallet key for our output. */ if (wallet_can_spend(ld->wallet, channel->shutdown_scriptpubkey[LOCAL], @@ -348,7 +386,7 @@ void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd) &channel->channel_info.remote_fundingkey, amount_msat_to_sat_round_down(channel->our_msat), amount_msat_to_sat_round_down(their_msat), - channel->our_config.dust_limit, feerate_perkw, local_wallet_index, + channel->our_config.dust_limit, feerate_for_close(channel), local_wallet_index, local_wallet_ext_key, channel->shutdown_scriptpubkey[LOCAL], channel->shutdown_scriptpubkey[REMOTE], channel->opener); diff --git a/lightningd/test/run-close_tx_check.c b/lightningd/test/run-close_tx_check.c index 6edad6d08f7f..d098e1ebd054 100644 --- a/lightningd/test/run-close_tx_check.c +++ b/lightningd/test/run-close_tx_check.c @@ -153,6 +153,7 @@ static const u8 op_return_burn[] = { /* Build a channel that only populates the fields close_tx_check reads. */ static struct channel *make_channel(const tal_t *ctx, const struct bitcoin_outpoint *funding, + struct amount_sat our_amt, const u8 *local_script, size_t local_len, const u8 *remote_script, size_t remote_len, bool simple_close_negotiated) @@ -180,17 +181,22 @@ static struct channel *make_channel(const tal_t *ctx, = tal_dup_arr(channel, u8, local_script, local_len, 0); channel->shutdown_scriptpubkey[REMOTE] = tal_dup_arr(channel, u8, remote_script, remote_len, 0); + if (!amount_sat_to_msat(&channel->our_msat, our_amt)) + abort(); return channel; } +/* closer gets 600k sat minus fee, closee gets 400ksat */ static struct bitcoin_tx *close_tx_with_scripts(const tal_t *ctx, const struct bitcoin_outpoint *funding, const u8 *closer_script, - const u8 *closee_script) + const u8 *closee_script, + u32 feerate) { struct pubkey pk1, pk2; const u8 *funding_wscript; struct bitcoin_tx *tx; + struct amount_sat to_closer; assert(pubkey_from_hexstr("034fede2c619f647fe7c01d40ae22e4c285291ca2ffb47937bbfb7d6e8285a081f", 2 * PUBKEY_CMPR_LEN, &pk1)); @@ -198,9 +204,13 @@ static struct bitcoin_tx *close_tx_with_scripts(const tal_t *ctx, 2 * PUBKEY_CMPR_LEN, &pk2)); funding_wscript = bitcoin_redeem_2of2(ctx, &pk1, &pk2); + to_closer = AMOUNT_SAT(600000); + if (!amount_sat_sub(&to_closer, to_closer, + amount_sat((u64)feerate * SIMPLE_CLOSE_WEIGHT / 1000))) + abort(); tx = create_simple_close_tx(ctx, NULL, NULL, closer_script, closee_script, funding_wscript, funding, AMOUNT_SAT(1000000), - AMOUNT_SAT(600000), AMOUNT_SAT(400000), 0); + to_closer, AMOUNT_SAT(400000), 0); assert(tx != NULL); tx->chainparams = chainparams; return tx; @@ -218,12 +228,12 @@ static void test_op_return_closer_accepted(void) const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); memset(&funding, 0, sizeof(funding)); - channel = make_channel(tmpctx, &funding, + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(400000), p2wpkh_local, sizeof(p2wpkh_local), p2wpkh_remote, sizeof(p2wpkh_remote), true); - tx = close_tx_with_scripts(tmpctx, &funding, closer, closee); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 0); - assert(close_tx_check(tmpctx, channel, tx) == NULL); + assert(close_tx_check(tmpctx, channel, tx, 0) == NULL); } /* Same tx, but option_simple_close was NOT negotiated: the OP_RETURN is not @@ -237,12 +247,12 @@ static void test_op_return_rejected_without_feature(void) const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); memset(&funding, 0, sizeof(funding)); - channel = make_channel(tmpctx, &funding, + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(600000), p2wpkh_local, sizeof(p2wpkh_local), p2wpkh_remote, sizeof(p2wpkh_remote), false); - tx = close_tx_with_scripts(tmpctx, &funding, closer, closee); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 0); - assert(close_tx_check(tmpctx, channel, tx) != NULL); + assert(close_tx_check(tmpctx, channel, tx, 0) != NULL); } /* A valid OP_RETURN script but with a non-zero value burns real funds; BOLT #2 @@ -256,10 +266,10 @@ static void test_op_return_nonzero_value_rejected(void) const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); memset(&funding, 0, sizeof(funding)); - channel = make_channel(tmpctx, &funding, + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(600000), p2wpkh_local, sizeof(p2wpkh_local), p2wpkh_remote, sizeof(p2wpkh_remote), true); - tx = close_tx_with_scripts(tmpctx, &funding, closer, closee); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 0); /* create_simple_close_tx forces the OP_RETURN value to zero; override it * to simulate a peer (or compromised subd) burning real funds. */ @@ -269,7 +279,7 @@ static void test_op_return_nonzero_value_rejected(void) tx->wtx->outputs[i].satoshi = 12345; } - assert(close_tx_check(tmpctx, channel, tx) != NULL); + assert(close_tx_check(tmpctx, channel, tx, 0) != NULL); } /* A non-OP_RETURN output that matches neither stored script is still an @@ -283,12 +293,12 @@ static void test_unknown_script_rejected(void) const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); memset(&funding, 0, sizeof(funding)); - channel = make_channel(tmpctx, &funding, + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(600000), p2wpkh_local, sizeof(p2wpkh_local), p2wpkh_remote, sizeof(p2wpkh_remote), true); - tx = close_tx_with_scripts(tmpctx, &funding, closer, closee); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 0); - assert(close_tx_check(tmpctx, channel, tx) != NULL); + assert(close_tx_check(tmpctx, channel, tx, 0) != NULL); } /* Ordinary close: both outputs go to the agreed shutdown scripts. */ @@ -301,12 +311,31 @@ static void test_known_scripts_accepted(void) const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); memset(&funding, 0, sizeof(funding)); - channel = make_channel(tmpctx, &funding, + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(400000), p2wpkh_local, sizeof(p2wpkh_local), p2wpkh_remote, sizeof(p2wpkh_remote), true); - tx = close_tx_with_scripts(tmpctx, &funding, closer, closee); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 0); - assert(close_tx_check(tmpctx, channel, tx) == NULL); + assert(close_tx_check(tmpctx, channel, tx, 0) == NULL); +} + +static void test_fee_too_high(void) +{ + struct bitcoin_outpoint funding; + struct channel *channel; + struct bitcoin_tx *tx; + const u8 *closee = tal_dup_arr(tmpctx, u8, p2wpkh_remote, sizeof(p2wpkh_remote), 0); + const u8 *closer = tal_dup_arr(tmpctx, u8, p2wpkh_local, sizeof(p2wpkh_local), 0); + + memset(&funding, 0, sizeof(funding)); + channel = make_channel(tmpctx, &funding, AMOUNT_SAT(600000), + p2wpkh_local, sizeof(p2wpkh_local), + p2wpkh_remote, sizeof(p2wpkh_remote), true); + tx = close_tx_with_scripts(tmpctx, &funding, closer, closee, 200); + + assert(close_tx_check(tmpctx, channel, tx, 0) != NULL); + assert(close_tx_check(tmpctx, channel, tx, 199) != NULL); + assert(close_tx_check(tmpctx, channel, tx, 200) == NULL); } int main(int argc, char *argv[]) @@ -319,6 +348,7 @@ int main(int argc, char *argv[]) test_op_return_nonzero_value_rejected(); test_unknown_script_rejected(); test_known_scripts_accepted(); + test_fee_too_high(); common_shutdown(); return 0; From 8e22298290df494d0ede980f43addf4405e1a017 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 14 Aug 2026 11:39:32 +0930 Subject: [PATCH 5/5] lightningd: don't save their closing tx. We want our own closing tx, but theirs might be too low-fee to use. Broadcast it, as a courtesy, but don't *rely* on it! Signed-off-by: Rusty Russell Changelog-EXPERIMENTAL: `experimental-simple-close` now doesn't save peer's closing transaction, so it can't be stuck with a too-low-fee tx. --- lightningd/peer_control.c | 23 +++++++++++++++++++ lightningd/peer_control.h | 5 ++++ lightningd/simple_close_control.c | 9 ++++---- lightningd/test/run-close_tx_check.c | 5 ++++ tests/test_closing.py | 34 +++++++++++++++++++++++++--- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index bfee7c084e6e..55aac6cab374 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -332,6 +332,29 @@ static struct bitcoin_tx *sign_and_send_last(const tal_t *ctx, return tx; } +/* Normally we only sign and broadcast our last_tx, but in the case of + * simple close, we want to broadcast theirs, but we don't bother + * saving it. We'll close the channel if/when we see it onchain. */ +void sign_and_broadcast_their_closing(struct channel *channel, + struct bitcoin_tx *tx, + const struct bitcoin_signature *their_sig) +{ + struct lightningd *ld = channel->peer->ld; + struct bitcoin_tx *signed_tx; + + /* We shouldn't get here, but in case we do. */ + if (channel->withheld) { + log_broken(channel->log, + "Withheld channel: should not have mutual close!"); + return; + } + + signed_tx = sign_last_tx(NULL, channel, tx, their_sig); + broadcast_tx(channel, ld->topology, channel, take(signed_tx), + cmd_id_from_close_command(tmpctx, ld, channel), + false, 0, NULL, NULL, NULL); +} + /* FIXME: reorder! */ static enum watch_result funding_spent(struct channel *channel, const struct bitcoin_tx *tx, diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index e5130ddd752b..def5e761f58f 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -129,6 +129,11 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel, bool cooperative, const struct bitcoin_tx *unilateral_tx); +/* Special case of sending their mutual close */ +void sign_and_broadcast_their_closing(struct channel *channel, + struct bitcoin_tx *tx, + const struct bitcoin_signature *their_sig); + void update_channel_from_inflight(struct lightningd *ld, struct channel *channel, const struct channel_inflight *inflight, diff --git a/lightningd/simple_close_control.c b/lightningd/simple_close_control.c index 968d4bb19988..d531b15cb5ca 100644 --- a/lightningd/simple_close_control.c +++ b/lightningd/simple_close_control.c @@ -177,7 +177,7 @@ static void handle_simpleclosed_our_closing_tx(struct channel *channel, const u8 bitcoin_txid(tx, &txid); log_info(channel->log, - "Simple close: stored closer tx %s", + "Simple close: stored our tx %s", fmt_bitcoin_txid(tmpctx, &txid)); subd_send_msg(channel->owner, @@ -189,7 +189,6 @@ static void handle_simpleclosed_our_closing_tx(struct channel *channel, const u8 static void handle_simpleclosed_their_closing_tx(struct channel *channel, const u8 *msg) { - struct lightningd *ld = channel->peer->ld; struct bitcoin_tx *tx; struct bitcoin_txid txid; struct bitcoin_signature sig; @@ -223,12 +222,12 @@ static void handle_simpleclosed_their_closing_tx(struct channel *channel, return; } - channel_set_last_tx(channel, tx, &sig); - wallet_channel_save(ld->wallet, channel); + /* We don't save their tx, we just broadcast it. */ + sign_and_broadcast_their_closing(channel, tx, &sig); bitcoin_txid(tx, &txid); log_info(channel->log, - "Simple close: stored closee tx %s", + "Simple close: broadcast their tx %s", fmt_bitcoin_txid(tmpctx, &txid)); } diff --git a/lightningd/test/run-close_tx_check.c b/lightningd/test/run-close_tx_check.c index d098e1ebd054..59c5d91f6c26 100644 --- a/lightningd/test/run-close_tx_check.c +++ b/lightningd/test/run-close_tx_check.c @@ -108,6 +108,11 @@ struct subd *new_channel_subd_(const tal_t *ctx UNNEEDED, void resolve_close_command(struct lightningd *ld UNNEEDED, struct channel *channel UNNEEDED, bool cooperative UNNEEDED, const struct bitcoin_tx **close_txs UNNEEDED) { fprintf(stderr, "resolve_close_command called!\n"); abort(); } +/* Generated stub for sign_and_broadcast_their_closing */ +void sign_and_broadcast_their_closing(struct channel *channel UNNEEDED, + struct bitcoin_tx *tx UNNEEDED, + const struct bitcoin_signature *their_sig UNNEEDED) +{ fprintf(stderr, "sign_and_broadcast_their_closing called!\n"); abort(); } /* Generated stub for simpleclosed_wire_name */ const char *simpleclosed_wire_name(int e UNNEEDED) { fprintf(stderr, "simpleclosed_wire_name called!\n"); abort(); } diff --git a/tests/test_closing.py b/tests/test_closing.py index 97690f53447b..afbe45724a87 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -4372,7 +4372,7 @@ def test_simple_close_restart(node_factory, bitcoind): def test_simple_close_closee_path(node_factory, bitcoind): """Each node acts as both closer and closee simultaneously. Verify that handle_simpleclosed_closee_broadcast runs on both nodes (confirmed by the - 'stored closee tx' log) so the peer's closing tx is persisted. Both + 'stored closee tx' log), and that the peer's own closing tx is persisted. Both nodes must claim their output from whichever tx wins the race.""" opts = {'experimental-simple-close': None} l1, l2 = node_factory.line_graph(2, opts=opts) @@ -4389,9 +4389,9 @@ def test_simple_close_closee_path(node_factory, bitcoind): # is received before closing_sig in the protocol), so use wait_for_logs to # find both in any order rather than two sequential wait_for_log calls. l1.daemon.wait_for_logs(['Simple close: stored closer tx', - 'Simple close: stored closee tx']) + 'Simple close: broadcast closee tx']) l2.daemon.wait_for_logs(['Simple close: stored closer tx', - 'Simple close: stored closee tx']) + 'Simple close: broadcast closee tx']) # One of the two conflicting txs confirms; both nodes must see their output. bitcoind.generate_block(1, wait_for_mempool=1) @@ -4402,6 +4402,34 @@ def test_simple_close_closee_path(node_factory, bitcoind): wait_for(lambda: confirmed_txid in {o['txid'] for o in l2.rpc.listfunds()['outputs']}) +def test_simple_close_closee_only(node_factory, bitcoind, executor): + """l1 doesn't get the signature for its own close, but DOES NOT persist the peer's tx.""" + # l2 has low fees, so txs are different. + opts = [{'experimental-simple-close': None}, + {'experimental-simple-close': None, + 'feerates': (1010, 1010, 1010, 1010, 1010), + 'disconnect': ['-WIRE_CLOSING_SIG']}] + l1, l2 = node_factory.line_graph(2, opts=opts) + + l1.pay(l2, 200000000) + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + # Get last tx now. + last_tx = only_one(l1.db_query("SELECT last_tx FROM channels;"))['last_tx'] + + # This won't exit yet. + executor.submit(l1.rpc.close, l2.info['id']) + l2.daemon.wait_for_log('dev_disconnect: -WIRE_CLOSING_SIG') + + l1.daemon.wait_for_log('Simple close: broadcast their tx') + time.sleep(1) + assert not l1.daemon.is_in_log('Simple close: stored our tx') + + # We do NOT update last_tx to their tx. + l1.stop() + assert only_one(l1.db_query("SELECT last_tx FROM channels;"))['last_tx'] == last_tx + + def test_simple_close_delay_broadcast(node_factory, bitcoind, executor): """When the closer has less output AND proposes a lower fee than the peer, it must log a 1-hour delay and let the peer's higher-fee tx get mined first.