diff --git a/channeld/channeld.c b/channeld/channeld.c index 11d8fdeea572..fc01c0f4a05f 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -2782,6 +2782,22 @@ static void handle_peer_fail_malformed_htlc(struct peer *peer, const u8 *msg) abort(); } +/* These messages are handled by simpleclosed, not channeld. They should + * never arrive here: if option_simple_close is negotiated, master starts + * simpleclosed (not closingd) after channeld sends shutdown_complete, so + * only simpleclosed talks to the peer at that point. */ +static void handle_peer_closing_complete(struct peer *peer, const u8 *msg) +{ + peer_failed_warn(peer->pps, &peer->channel_id, + "Unexpected closing_complete in channeld"); +} + +static void handle_peer_closing_sig(struct peer *peer, const u8 *msg) +{ + peer_failed_warn(peer->pps, &peer->channel_id, + "Unexpected closing_sig in channeld"); +} + static void handle_peer_shutdown(struct peer *peer, const u8 *shutdown) { struct channel_id channel_id; @@ -5202,14 +5218,18 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_TX_ABORT: check_tx_abort(peer, msg, NULL); return; + case WIRE_CLOSING_COMPLETE: + handle_peer_closing_complete(peer, msg); + return; + case WIRE_CLOSING_SIG: + handle_peer_closing_sig(peer, msg); + return; case WIRE_INIT: case WIRE_OPEN_CHANNEL: case WIRE_ACCEPT_CHANNEL: case WIRE_FUNDING_CREATED: case WIRE_FUNDING_SIGNED: case WIRE_CLOSING_SIGNED: - case WIRE_CLOSING_COMPLETE: - case WIRE_CLOSING_SIG: case WIRE_TX_ADD_INPUT: case WIRE_TX_REMOVE_INPUT: case WIRE_TX_ADD_OUTPUT: diff --git a/closingd/Makefile b/closingd/Makefile index 4d961d289d37..a128163f2756 100644 --- a/closingd/Makefile +++ b/closingd/Makefile @@ -8,15 +8,26 @@ CLOSINGD_SRC := closingd/closingd.c \ CLOSINGD_OBJS := $(CLOSINGD_SRC:.c=.o) $(CLOSINGD_OBJS): $(CLOSINGD_HEADERS) +# Simple close daemon +SIMPLECLOSED_HEADERS := closingd/simpleclosed_wiregen.h + +SIMPLECLOSED_SRC := closingd/simpleclosed.c \ + $(SIMPLECLOSED_HEADERS:.h=.c) + +SIMPLECLOSED_OBJS := $(SIMPLECLOSED_SRC:.c=.o) +$(SIMPLECLOSED_OBJS): $(SIMPLECLOSED_HEADERS) + # Make sure these depend on everything. -ALL_C_SOURCES += $(CLOSINGD_SRC) -ALL_C_HEADERS += $(CLOSINGD_HEADERS) -ALL_PROGRAMS += lightningd/lightning_closingd +ALL_C_SOURCES += $(CLOSINGD_SRC) $(SIMPLECLOSED_SRC) +ALL_C_HEADERS += $(CLOSINGD_HEADERS) $(SIMPLECLOSED_HEADERS) +ALL_PROGRAMS += lightningd/lightning_closingd lightningd/lightning_simpleclosed # Here's what lightningd depends on -LIGHTNINGD_CONTROL_HEADERS += closingd/closingd_wiregen.h -LIGHTNINGD_CONTROL_OBJS += closingd/closingd_wiregen.o +LIGHTNINGD_CONTROL_HEADERS += closingd/closingd_wiregen.h closingd/simpleclosed_wiregen.h +LIGHTNINGD_CONTROL_OBJS += closingd/closingd_wiregen.o closingd/simpleclosed_wiregen.o lightningd/lightning_closingd: $(CLOSINGD_OBJS) $(HSMD_CLIENT_OBJS) libcommon.a +lightningd/lightning_simpleclosed: $(SIMPLECLOSED_OBJS) $(HSMD_CLIENT_OBJS) libcommon.a + -include closingd/test/Makefile diff --git a/closingd/simpleclosed.c b/closingd/simpleclosed.c new file mode 100644 index 000000000000..3eeba0890708 --- /dev/null +++ b/closingd/simpleclosed.c @@ -0,0 +1,875 @@ +/* Main simple-close daemon: implements option_simple_close (BOLT #2). + * Runs after channeld signals shutdown_complete, replacing closingd + * when option_simple_close is negotiated. */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stdin == requests from master, 3 == peer fd, 4 == hsmd fd */ +#define REQ_FD STDIN_FILENO +#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; + if (!wire_sync_write(HSM_FD, req)) + status_failed(STATUS_FAIL_HSM_IO, + "Writing to HSM: %s", strerror(errno)); + msg = wire_sync_read(ctx, HSM_FD); + if (!msg) + status_failed(STATUS_FAIL_HSM_IO, + "Reading from HSM: %s", strerror(errno)); + return msg; +} + +/* Tell master we got peer's closing_sig for our tx; block for txid reply. */ +static struct bitcoin_txid master_got_sig(struct per_peer_state *pps, + struct bitcoin_tx *tx) +{ + struct bitcoin_txid txid; + u8 *msg; + + msg = towire_simpleclosed_got_sig(tmpctx, tx); + if (!wire_sync_write(REQ_FD, take(msg))) + status_failed(STATUS_FAIL_MASTER_IO, + "Writing got_sig: %s", strerror(errno)); + + msg = wire_sync_read(tmpctx, REQ_FD); + if (!msg) + status_failed(STATUS_FAIL_MASTER_IO, + "Reading got_sig_reply: %s", strerror(errno)); + if (!fromwire_simpleclosed_got_sig_reply(msg, &txid)) + status_failed(STATUS_FAIL_MASTER_IO, + "Bad got_sig_reply: %s", tal_hex(tmpctx, msg)); + return txid; +} + +/* Build one closing tx variant. closer_script or closee_script may be NULL + * to omit that output. */ +static struct bitcoin_tx *make_close_tx(const tal_t *ctx, + const struct chainparams *chainparams, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + const u8 *closer_script, + const u8 *closee_script, + const u8 *funding_wscript, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + struct amount_sat closer_amount, + struct amount_sat closee_amount, + u32 locktime) +{ + return create_simple_close_tx(ctx, chainparams, + local_wallet_index, local_wallet_ext_key, + closer_script, closee_script, + funding_wscript, funding, funding_sats, + closer_amount, closee_amount, locktime); +} + +/* Sign a simple close tx via HSM; returns bitcoin_signature. */ +static struct bitcoin_signature sign_tx(const tal_t *ctx, + struct bitcoin_tx *tx, + const struct pubkey *remote_fundingkey) +{ + struct bitcoin_signature sig; + const u8 *msg; + + msg = hsm_req(tmpctx, + take(towire_hsmd_sign_mutual_close_tx(NULL, tx, + remote_fundingkey))); + if (!fromwire_hsmd_sign_tx_reply(msg, &sig)) + status_failed(STATUS_FAIL_HSM_IO, + "Bad sign_mutual_close_tx reply %s", + tal_hex(tmpctx, msg)); + return sig; +} + +/* Is this script an OP_RETURN (dust exempt, zero value)? */ +static bool is_op_return_script(const u8 *script) +{ + return tal_count(script) > 0 && script[0] == 0x6a /* OP_RETURN */; +} + +/* Build and send closing_complete as the closer. + * Returns the TLVs we sent (so we can validate the closing_sig reply). + * Also writes the computed fee into *fee_out. */ +static struct tlv_closing_tlvs *send_closing_complete( + struct per_peer_state *pps, + const struct channel_id *channel_id, + const struct chainparams *chainparams, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + const struct pubkey *local_fundingkey, + const struct pubkey *remote_fundingkey, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + struct amount_sat local_sat, + struct amount_sat remote_sat, + struct amount_sat dust_limit, + u32 feerate_perkw, + const u8 *local_script, + const u8 *remote_script, + u32 locktime, + struct amount_sat *fee_out) +{ + const u8 *funding_wscript; + struct amount_sat fee, closer_amount, closee_amount; + bool is_lesser, closee_dust, closer_dust; + struct bitcoin_tx *tx_both, *tx_closer_only, *tx_closee_only; + struct bitcoin_signature sig; + struct tlv_closing_tlvs *tlvs; + u8 *msg; + + funding_wscript = bitcoin_redeem_2of2(tmpctx, + local_fundingkey, + remote_fundingkey); + + /* Fee: feerate * weight / 1000, capped at our balance. */ + fee = amount_sat((u64)feerate_perkw * SIMPLE_CLOSE_WEIGHT / 1000); + if (amount_sat_greater(fee, local_sat)) + fee = local_sat; /* Can't pay more than we have */ + + if (!amount_sat_sub(&closer_amount, local_sat, fee)) + closer_amount = AMOUNT_SAT(0); + closee_amount = remote_sat; + + is_lesser = amount_sat_less(local_sat, remote_sat); + closee_dust = amount_sat_less(closee_amount, dust_limit) + && !is_op_return_script(remote_script); + closer_dust = amount_sat_less(closer_amount, dust_limit) + && !is_op_return_script(local_script); + + tlvs = tlv_closing_tlvs_new(tmpctx); + + /* BOLT #2: + * The sender of `closing_complete` (aka. "the closer"): + * ... + * - If the local outstanding balance (in millisatoshi) is less than the remote outstanding balance: + * - MUST NOT set `closer_output_only`. + * - MUST set `closee_output_only` if the local output amount is dust. + * - MAY set `closee_output_only` if it considers the local output amount uneconomical AND its `closer_scriptpubkey` is not `OP_RETURN`. + * - Otherwise (not lesser amount, cannot remove its own output): + * - MUST NOT set `closee_output_only`. + * - If it considers the local output amount uneconomical: + * - MAY send a `closer_scriptpubkey` that is a valid `OP_RETURN` script. + * - If it does, the output value MUST be set to zero so that all funds go to fees, as specified in [BOLT #3]... + * - If the closee's output amount is dust: + * - MUST set `closer_output_only`. + * - MUST NOT set `closer_and_closee_outputs`. + * - Otherwise: + * - MUST set both `closer_output_only` and `closer_and_closee_outputs`. + */ + if (is_lesser) { + status_debug("We are lesser: local %s remote %s", fmt_amount_sat(tmpctx, local_sat), fmt_amount_sat(tmpctx, remote_sat)); + if (closer_dust) { + /* Our output is dust: sign tx with only closee output */ + tx_closee_only = make_close_tx(tmpctx, chainparams, + NULL, NULL, + NULL, remote_script, + funding_wscript, funding, + funding_sats, + AMOUNT_SAT(0), closee_amount, + locktime); + if (tx_closee_only) { + sig = sign_tx(tmpctx, tx_closee_only, remote_fundingkey); + tlvs->closee_output_only = tal(tlvs, secp256k1_ecdsa_signature); + *tlvs->closee_output_only = sig.s; + status_debug("send_closing_complete: our sig is closee_output_only: %s", tal_hex(tmpctx, tlvs->closee_output_only)); + } + } else { + /* Both outputs present; closee can use this. */ + tx_both = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + local_script, remote_script, + funding_wscript, funding, + funding_sats, + closer_amount, closee_amount, + locktime); + if (tx_both) { + sig = sign_tx(tmpctx, tx_both, remote_fundingkey); + tlvs->closer_and_closee_outputs = tal(tlvs, + secp256k1_ecdsa_signature); + *tlvs->closer_and_closee_outputs = sig.s; + status_debug("send_closing_complete: our sig is closer_and_closee_outputs: %s", tal_hex(tmpctx, tlvs->closer_and_closee_outputs)); + } + } + } else { + status_debug("We are NOT lesser: local %s remote %s", fmt_amount_sat(tmpctx, local_sat), fmt_amount_sat(tmpctx, remote_sat)); + /* Not lesser: MUST set closer_output_only always. */ + tx_closer_only = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + local_script, NULL, + funding_wscript, funding, + funding_sats, + closer_amount, AMOUNT_SAT(0), + locktime); + if (tx_closer_only) { + sig = sign_tx(tmpctx, tx_closer_only, remote_fundingkey); + tlvs->closer_output_only = tal(tlvs, + secp256k1_ecdsa_signature); + *tlvs->closer_output_only = sig.s; + status_debug("send_closing_complete: our sig is closer_output_only: %s", tal_hex(tmpctx, tlvs->closer_output_only)); + } + + if (!closee_dust) { + /* Also sign the both-outputs variant. */ + tx_both = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + local_script, remote_script, + funding_wscript, funding, + funding_sats, + closer_amount, closee_amount, + locktime); + if (tx_both) { + sig = sign_tx(tmpctx, tx_both, remote_fundingkey); + tlvs->closer_and_closee_outputs = tal(tlvs, + secp256k1_ecdsa_signature); + *tlvs->closer_and_closee_outputs = sig.s; + status_debug("send_closing_complete: our sig is closer_and_closee_outputs: %s", tal_hex(tmpctx, tlvs->closer_and_closee_outputs)); + } + } + } + + /* Sanity: must have at least one sig to send. */ + if (!tlvs->closer_output_only && !tlvs->closee_output_only + && !tlvs->closer_and_closee_outputs) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "No valid closing tx variant could be built " + "(local=%s remote=%s fee=%s dust=%s)", + fmt_amount_sat(tmpctx, local_sat), + fmt_amount_sat(tmpctx, remote_sat), + fmt_amount_sat(tmpctx, fee), + fmt_amount_sat(tmpctx, dust_limit)); + + msg = towire_closing_complete(tmpctx, channel_id, + local_script, remote_script, + fee, locktime, tlvs); + peer_write(pps, take(msg)); + + *fee_out = fee; + status_debug("Sent closing_complete: closer=%s closee=%s fee=%s", + fmt_amount_sat(tmpctx, closer_amount), + fmt_amount_sat(tmpctx, closee_amount), + fmt_amount_sat(tmpctx, fee)); + return tlvs; +} + +/* As closee: receive peer's closing_complete, sign the right variant, + * send closing_sig, and broadcast. Returns the tx we signed. */ +static struct bitcoin_tx *handle_closing_complete( + struct per_peer_state *pps, + const struct channel_id *cid, + const struct chainparams *chainparams, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + const struct pubkey *local_fundingkey, + const struct pubkey *remote_fundingkey, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + struct amount_sat local_sat, + struct amount_sat dust_limit, + const u8 *our_last_script, + const u8 *msg) +{ + struct channel_id their_cid; + u8 *closer_script, *closee_script; + struct amount_sat fee_sat; + u32 locktime; + struct tlv_closing_tlvs *tlvs; + struct tlv_closing_tlvs *reply_tlvs; + struct bitcoin_tx *chosen_tx; + struct bitcoin_signature sig; + struct amount_sat closer_amount, closee_amount; + const u8 *funding_wscript; + u8 *reply; + bool anysegwit = true, option_simple_close = true; + struct amount_sat remote_sat; + + if (!fromwire_closing_complete(tmpctx, msg, &their_cid, + &closer_script, &closee_script, + &fee_sat, &locktime, &tlvs)) + peer_failed_warn(pps, cid, + "Bad closing_complete: %s", + tal_hex(tmpctx, msg)); + + if (!channel_id_eq(&their_cid, cid)) + peer_failed_err(pps, &their_cid, + "Wrong channel_id in closing_complete"); + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * ... + * - If `closee_scriptpubkey` does not match the last script it sent (from `closing_complete` or from the initial `shutdown`): + * - SHOULD ignore `closing_complete`. + * - SHOULD send a `warning`. + * - SHOULD close the connection. + */ + if (!memeq(closee_script, tal_count(closee_script), + our_last_script, tal_count(our_last_script))) { + peer_failed_warn(pps, cid, + "closing_complete closee_script %s != our script %s: " + "reconnect to resync", + tal_hex(tmpctx, closee_script), + tal_hex(tmpctx, our_last_script)); + } + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * ... + * - If `closer_scriptpubkey` is invalid (as detailed in the [`shutdown` requirements](#closing-initiation-shutdown)): + * - SHOULD ignore `closing_complete`. + * - SHOULD send a `warning`. + * - SHOULD close the connection. + */ + if (!valid_shutdown_scriptpubkey(closer_script, anysegwit, + false, option_simple_close)) + peer_failed_warn(pps, cid, + "Invalid closer_scriptpubkey in closing_complete: %s", + tal_hex(tmpctx, closer_script)); + + funding_wscript = bitcoin_redeem_2of2(tmpctx, local_fundingkey, + remote_fundingkey); + + /* Compute amounts: closer pays fee, closee gets remote_sat. */ + if (!amount_sat_sub(&remote_sat, funding_sats, local_sat)) + remote_sat = AMOUNT_SAT(0); + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * - If `fee_satoshis` is greater than the closer's outstanding balance: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + */ + if (is_op_return_script(closer_script)) + closer_amount = AMOUNT_SAT(0); + else if (!amount_sat_sub(&closer_amount, remote_sat, fee_sat)) + peer_failed_warn(pps, cid, + "Closer fee %s exceeds their balance %s", + fmt_amount_sat(tmpctx, fee_sat), + fmt_amount_sat(tmpctx, remote_sat)); + + if (is_op_return_script(closee_script)) + closee_amount = AMOUNT_SAT(0); + else + closee_amount = local_sat; + + bool our_output_dust = amount_sat_less(closee_amount, dust_limit) + && !is_op_return_script(closee_script); + + reply_tlvs = tlv_closing_tlvs_new(tmpctx); + + struct bitcoin_signature their_sig; + their_sig.sighash_type = SIGHASH_ALL; + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * ... + * - Select a signature for validation: + * - If the local output amount is dust: + * - MUST use `closer_output_only`. + * - Otherwise, if it considers the local output amount uneconomical AND its `closee_scriptpubkey` is not `OP_RETURN`: + * - MUST use `closer_output_only`. + * - Otherwise, if `closer_and_closee_outputs` is present: + * - MUST use `closer_and_closee_outputs`. + * - Otherwise: + * - MUST use `closee_output_only`. + * - If the selected signature field does not exist: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + * - If the signature field is not valid for the corresponding closing transaction specified in [BOLT #3]...: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + */ + if (our_output_dust) { + if (!tlvs->closer_output_only) + peer_failed_warn(pps, cid, + "Our output is dust but no " + "closer_output_only sig provided"); + chosen_tx = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + closer_script, NULL, + funding_wscript, funding, funding_sats, + closer_amount, AMOUNT_SAT(0), + locktime); + sig = sign_tx(tmpctx, chosen_tx, remote_fundingkey); + reply_tlvs->closer_output_only = tal(reply_tlvs, + secp256k1_ecdsa_signature); + *reply_tlvs->closer_output_only = sig.s; + status_debug("handle_closing_complete: our sig is closer_output_only: %s", tal_hex(tmpctx, reply_tlvs->closer_output_only)); + + their_sig.s = *tlvs->closer_output_only; + if (!check_tx_sig(chosen_tx, 0, NULL, funding_wscript, + remote_fundingkey, &their_sig)) + peer_failed_warn(pps, cid, + "Bad closer_output_only sig in " + "closing_complete"); + } else if (tlvs->closer_and_closee_outputs) { + chosen_tx = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + closer_script, closee_script, + funding_wscript, funding, funding_sats, + closer_amount, closee_amount, + locktime); + sig = sign_tx(tmpctx, chosen_tx, remote_fundingkey); + reply_tlvs->closer_and_closee_outputs = tal(reply_tlvs, + secp256k1_ecdsa_signature); + *reply_tlvs->closer_and_closee_outputs = sig.s; + status_debug("handle_closing_complete: our sig is closer_and_closee_outputs: %s", tal_hex(tmpctx, reply_tlvs->closer_and_closee_outputs)); + their_sig.s = *tlvs->closer_and_closee_outputs; + if (!check_tx_sig(chosen_tx, 0, NULL, funding_wscript, + remote_fundingkey, &their_sig)) + peer_failed_warn(pps, cid, + "Bad closer_and_closee_outputs sig in " + "closing_complete"); + } else { + if (!tlvs->closee_output_only) + peer_failed_warn(pps, cid, + "No valid sig variant in " + "closing_complete"); + chosen_tx = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + NULL, closee_script, + funding_wscript, funding, funding_sats, + AMOUNT_SAT(0), closee_amount, + locktime); + sig = sign_tx(tmpctx, chosen_tx, remote_fundingkey); + reply_tlvs->closee_output_only = tal(reply_tlvs, + secp256k1_ecdsa_signature); + *reply_tlvs->closee_output_only = sig.s; + status_debug("handle_closing_complete: our sig is closee_output_only: %s", tal_hex(tmpctx, reply_tlvs->closee_output_only)); + + their_sig.s = *tlvs->closee_output_only; + if (!check_tx_sig(chosen_tx, 0, NULL, funding_wscript, + remote_fundingkey, &their_sig)) + peer_failed_warn(pps, cid, + "Bad closee_output_only sig in " + "closing_complete"); + } + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * ... + * - MUST sign and broadcast the corresponding closing transaction. + */ + bitcoin_tx_input_set_witness(chosen_tx, 0, + take(bitcoin_witness_2of2(NULL, &sig, &their_sig, + local_fundingkey, + remote_fundingkey))); + + /* BOLT #2: + * The receiver of `closing_complete` (aka. "the closee"): + * ... + * - MUST send `closing_sig` with a single valid signature in the same TLV field as the `closing_complete`. + */ + reply = towire_closing_sig(tmpctx, cid, + closer_script, closee_script, + fee_sat, locktime, reply_tlvs); + peer_write(pps, take(reply)); + + status_debug("Sent closing_sig (as closee), broadcasting tx"); + + /* Tell master to broadcast. */ + wire_sync_write(REQ_FD, + take(towire_simpleclosed_closee_broadcast(NULL, + chosen_tx))); + return chosen_tx; +} + +/* As closer: validate the closing_sig we got back. Returns the closing tx. */ +static struct bitcoin_tx *handle_closing_sig( + struct per_peer_state *pps, + const struct channel_id *cid, + const struct chainparams *chainparams, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + const struct pubkey *local_fundingkey, + const struct pubkey *remote_fundingkey, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + struct amount_sat local_sat, + struct amount_sat remote_sat, + struct amount_sat dust_limit, + const u8 *sent_closer_script, + const u8 *sent_closee_script, + struct amount_sat sent_fee, + u32 sent_locktime, + const struct tlv_closing_tlvs *sent_tlvs, + const u8 *msg) +{ + struct channel_id their_cid; + u8 *closer_script, *closee_script; + struct amount_sat fee_sat; + u32 locktime; + struct tlv_closing_tlvs *tlvs; + const u8 *funding_wscript; + struct amount_sat closer_amount, closee_amount; + + if (!fromwire_closing_sig(tmpctx, msg, &their_cid, + &closer_script, &closee_script, + &fee_sat, &locktime, &tlvs)) + peer_failed_warn(pps, cid, + "Bad closing_sig: %s", tal_hex(tmpctx, msg)); + + if (!channel_id_eq(&their_cid, cid)) + peer_failed_err(pps, &their_cid, + "Wrong channel_id in closing_sig"); + + /* BOLT #2: + * The receiver of `closing_sig`: + * - If `closer_scriptpubkey`, `closee_scriptpubkey`, `fee_satoshis` or `locktime` don't match what was sent in `closing_complete`: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + */ + if (!memeq(closer_script, tal_count(closer_script), + sent_closer_script, tal_count(sent_closer_script)) + || !memeq(closee_script, tal_count(closee_script), + sent_closee_script, tal_count(sent_closee_script)) + || !amount_sat_eq(fee_sat, sent_fee) + || locktime != sent_locktime) + peer_failed_warn(pps, cid, + "closing_sig fields don't match our " + "closing_complete (script/fee/locktime mismatch)"); + + /* BOLT #2: + * The receiver of `closing_sig`: + * ... + * - If `tlvs` does not contain exactly one signature: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + */ + int nsigs = (tlvs->closer_output_only ? 1 : 0) + + (tlvs->closee_output_only ? 1 : 0) + + (tlvs->closer_and_closee_outputs ? 1 : 0); + if (nsigs != 1) + peer_failed_warn(pps, cid, + "closing_sig must have exactly one sig, got %d", + nsigs); + + if (tlvs->closer_output_only && !sent_tlvs->closer_output_only) + peer_failed_warn(pps, cid, + "closing_sig closer_output_only not in our " + "closing_complete"); + if (tlvs->closee_output_only && !sent_tlvs->closee_output_only) + peer_failed_warn(pps, cid, + "closing_sig closee_output_only not in our " + "closing_complete"); + if (tlvs->closer_and_closee_outputs + && !sent_tlvs->closer_and_closee_outputs) + peer_failed_warn(pps, cid, + "closing_sig closer_and_closee_outputs not in " + "our closing_complete"); + + funding_wscript = bitcoin_redeem_2of2(tmpctx, local_fundingkey, + remote_fundingkey); + + if (is_op_return_script(closer_script)) + closer_amount = AMOUNT_SAT(0); + else if (!amount_sat_sub(&closer_amount, local_sat, fee_sat)) + closer_amount = AMOUNT_SAT(0); + + if (is_op_return_script(closee_script)) + closee_amount = AMOUNT_SAT(0); + else + closee_amount = remote_sat; + + /* Reconstruct the tx that was signed. */ + struct bitcoin_tx *signed_tx = NULL; + struct bitcoin_signature their_sig; + their_sig.sighash_type = SIGHASH_ALL; + + /* Prepare our signature */ + struct bitcoin_signature our_sig; + our_sig.sighash_type = SIGHASH_ALL; + + // FIXME: If we don't have our signatures ready we need to build our signature now + if (tlvs->closer_output_only) { + status_debug("their sig is closer_output_only"); + their_sig.s = *tlvs->closer_output_only; + signed_tx = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + closer_script, NULL, + funding_wscript, funding, funding_sats, + closer_amount, AMOUNT_SAT(0), + locktime); + our_sig.s = *sent_tlvs->closer_output_only; + } else if (tlvs->closee_output_only) { + status_debug("their sig is closee_output_only"); + their_sig.s = *tlvs->closee_output_only; + signed_tx = make_close_tx(tmpctx, chainparams, + NULL, NULL, + NULL, closee_script, + funding_wscript, funding, funding_sats, + AMOUNT_SAT(0), closee_amount, + locktime); + our_sig.s = *sent_tlvs->closee_output_only; + } else { + status_debug("their sig is closer_and_closee_outputs"); + their_sig.s = *tlvs->closer_and_closee_outputs; + signed_tx = make_close_tx(tmpctx, chainparams, + local_wallet_index, + local_wallet_ext_key, + closer_script, closee_script, + funding_wscript, funding, funding_sats, + closer_amount, closee_amount, + locktime); + our_sig.s = *sent_tlvs->closer_and_closee_outputs; + } + + if (!signed_tx) + peer_failed_warn(pps, cid, + "Could not reconstruct closing tx from " + "closing_sig fields"); + + /* BOLT #2: + * The receiver of `closing_sig`: + * ... + * - If the signature field is not valid for the corresponding closing transaction specified in [BOLT #3]...: + * - MUST either send a `warning` and close the connection, or send an `error` and fail the channel. + */ + if (!check_tx_sig(signed_tx, 0, NULL, funding_wscript, + remote_fundingkey, &their_sig)) + peer_failed_warn(pps, cid, + "Bad signature in closing_sig: %s", + tal_hex(tmpctx, msg)); + + /* Apply the complete 2-of-2 witness: our sig (closer/local) and + * the closee's sig (remote). bitcoin_witness_2of2 orders them by + * pubkey so the witness matches the multisig script. */ + bitcoin_tx_input_set_witness(signed_tx, 0, + take(bitcoin_witness_2of2(NULL, &our_sig, &their_sig, + local_fundingkey, + remote_fundingkey))); + + status_debug("closing_sig verified OK, tx ready to broadcast"); + return signed_tx; +} + +int main(int argc, char *argv[]) +{ + setup_locale(); + + const tal_t *ctx = tal(NULL, char); + struct per_peer_state *pps; + u8 *msg; + struct pubkey local_fundingkey, remote_fundingkey; + struct bitcoin_outpoint funding; + struct amount_sat funding_sats, local_sat, remote_sat, dust_limit; + u32 min_feerate_perkw, max_feerate_perkw; + u32 *local_wallet_index; + struct ext_key *local_wallet_ext_key; + u8 *local_script, *remote_script; + struct channel_id channel_id; + enum side opener; + bool got_peer_complete, got_our_sig; + struct tlv_closing_tlvs *sent_tlvs; + u8 *sent_closer_script, *sent_closee_script; + struct amount_sat sent_fee; + u32 sent_locktime = 0; + + subdaemon_setup(argc, argv); + + status_setup_sync(REQ_FD); + + msg = wire_sync_read(tmpctx, REQ_FD); + if (!fromwire_simpleclosed_init(ctx, msg, + &chainparams, + &channel_id, + &funding, + &funding_sats, + &local_fundingkey, + &remote_fundingkey, + &local_sat, + &remote_sat, + &dust_limit, + &min_feerate_perkw, + &max_feerate_perkw, + &local_wallet_index, + &local_wallet_ext_key, + &local_script, + &remote_script, + &opener)) + status_failed(STATUS_FAIL_MASTER_IO, + "Bad simpleclosed_init: %s", + tal_hex(tmpctx, msg)); + + /* stdin == requests, 3 == peer, 4 = hsmd */ + pps = notleak(new_per_peer_state(ctx)); + per_peer_state_set_fd(pps, 3); + + status_debug("Simple close starting: local=%s remote=%s", + fmt_amount_sat(tmpctx, local_sat), + fmt_amount_sat(tmpctx, remote_sat)); + + /* Send our own closing_complete as the closer. */ + sent_closer_script = local_script; + sent_closee_script = remote_script; + sent_locktime = 0; /* nLockTime = 0 for initial proposal */ + + sent_tlvs = send_closing_complete(pps, &channel_id, chainparams, + &funding, funding_sats, + &local_fundingkey, &remote_fundingkey, + local_wallet_index, local_wallet_ext_key, + local_sat, remote_sat, dust_limit, + min_feerate_perkw, + local_script, remote_script, + sent_locktime, + &sent_fee); + + /* Exchange loop: wait for peer's closing_complete and our closing_sig. */ + got_peer_complete = false; + got_our_sig = false; + + while (!got_peer_complete || !got_our_sig) { + clean_tmpctx(); + msg = peer_read(tmpctx, pps); + + switch (fromwire_peektype(msg)) { + case WIRE_CLOSING_COMPLETE: + /* Peer sends us their closing_complete: we are the closee. */ + if (got_peer_complete) { + status_debug("Got another closing_complete " + "(peer bumping fee): re-signing"); + } + handle_closing_complete(pps, &channel_id, chainparams, + &funding, funding_sats, + &local_fundingkey, + &remote_fundingkey, + local_wallet_index, + local_wallet_ext_key, + local_sat, dust_limit, + local_script, msg); + got_peer_complete = true; + break; + + case WIRE_CLOSING_SIG: { + /* Peer replies to our closing_complete: we are the closer. */ + struct bitcoin_tx *closing_tx; + struct bitcoin_txid txid; + + closing_tx = handle_closing_sig(pps, &channel_id, + chainparams, + &funding, funding_sats, + &local_fundingkey, + &remote_fundingkey, + 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); + txid = master_got_sig(pps, closing_tx); + status_debug("Closer tx broadcast: %s", + fmt_bitcoin_txid(tmpctx, &txid)); + got_our_sig = true; + break; + } + + case WIRE_CLOSING_SIGNED: + /* Peer is still running legacy close — shouldn't happen + * if option_simple_close was negotiated. */ + peer_failed_warn(pps, &channel_id, + "Got legacy closing_signed but " + "option_simple_close was negotiated"); + break; + + /* These are all swallowed by connectd */ + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_CHANNEL_ANNOUNCEMENT: + case WIRE_CHANNEL_UPDATE: + case WIRE_NODE_ANNOUNCEMENT: + case WIRE_QUERY_SHORT_CHANNEL_IDS: + case WIRE_QUERY_CHANNEL_RANGE: + case WIRE_REPLY_CHANNEL_RANGE: + case WIRE_GOSSIP_TIMESTAMP_FILTER: + case WIRE_REPLY_SHORT_CHANNEL_IDS_END: + case WIRE_PING: + case WIRE_PONG: + case WIRE_WARNING: + case WIRE_ERROR: + case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_PEER_STORAGE_RETRIEVAL: + /* Ignore unknown messages (e.g. gossip). */ + status_debug("Ignoring message type %d in simple close", + fromwire_peektype(msg)); + break; + + /* These are unexpected messages. Warn the peer */ + case WIRE_INIT: + case WIRE_TX_ADD_INPUT: + case WIRE_TX_ADD_OUTPUT: + case WIRE_TX_REMOVE_INPUT: + case WIRE_TX_REMOVE_OUTPUT: + case WIRE_TX_COMPLETE: + case WIRE_TX_SIGNATURES: + case WIRE_TX_INIT_RBF: + case WIRE_TX_ACK_RBF: + case WIRE_TX_ABORT: + case WIRE_OPEN_CHANNEL: + case WIRE_ACCEPT_CHANNEL: + case WIRE_FUNDING_CREATED: + case WIRE_FUNDING_SIGNED: + case WIRE_CHANNEL_READY: + case WIRE_OPEN_CHANNEL2: + case WIRE_ACCEPT_CHANNEL2: + case WIRE_STFU: + case WIRE_SPLICE: + case WIRE_SPLICE_ACK: + case WIRE_SPLICE_LOCKED: + case WIRE_SHUTDOWN: + case WIRE_UPDATE_ADD_HTLC: + case WIRE_UPDATE_FULFILL_HTLC: + case WIRE_UPDATE_FAIL_HTLC: + case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_START_BATCH: + case WIRE_COMMITMENT_SIGNED: + case WIRE_REVOKE_AND_ACK: + case WIRE_UPDATE_FEE: + case WIRE_UPDATE_BLOCKHEIGHT: + case WIRE_CHANNEL_REESTABLISH: + case WIRE_ANNOUNCEMENT_SIGNATURES: + peer_failed_warn(pps, &channel_id, + "Peer sent unexpected message %u (%s)", + fromwire_peektype(msg), + peer_wire_name(fromwire_peektype(msg))); + break; + } + } + + wire_sync_write(REQ_FD, take(towire_simpleclosed_complete(NULL))); + tal_free(ctx); + daemon_shutdown(); + return 0; +} diff --git a/closingd/simpleclosed_wire.csv b/closingd/simpleclosed_wire.csv new file mode 100644 index 000000000000..be4ecb07d46e --- /dev/null +++ b/closingd/simpleclosed_wire.csv @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +# Begin! (passes peer_fd and hsm_fd) +msgtype,simpleclosed_init,3001 +msgdata,simpleclosed_init,chainparams,chainparams, +msgdata,simpleclosed_init,channel_id,channel_id, +msgdata,simpleclosed_init,funding,bitcoin_outpoint, +msgdata,simpleclosed_init,funding_satoshi,amount_sat, +msgdata,simpleclosed_init,local_fundingkey,pubkey, +msgdata,simpleclosed_init,remote_fundingkey,pubkey, +msgdata,simpleclosed_init,local_sat,amount_sat, +msgdata,simpleclosed_init,remote_sat,amount_sat, +msgdata,simpleclosed_init,our_dust_limit,amount_sat, +msgdata,simpleclosed_init,min_feerate_perkw,u32, +msgdata,simpleclosed_init,max_feerate_perkw,u32, +msgdata,simpleclosed_init,local_wallet_index,?u32, +msgdata,simpleclosed_init,local_wallet_ext_key,?ext_key, +msgdata,simpleclosed_init,local_scriptpubkey_len,u16, +msgdata,simpleclosed_init,local_scriptpubkey,u8,local_scriptpubkey_len +msgdata,simpleclosed_init,remote_scriptpubkey_len,u16, +msgdata,simpleclosed_init,remote_scriptpubkey,u8,remote_scriptpubkey_len +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, + +# Master replies with the txid (after storing/broadcasting). +msgtype,simpleclosed_got_sig_reply,3102 +msgdata,simpleclosed_got_sig_reply,closing_txid,bitcoin_txid, + +# simpleclosed tells master it signed and broadcast the peer's closing tx +# (as the closee). +msgtype,simpleclosed_closee_broadcast,3003 +msgdata,simpleclosed_closee_broadcast,tx,bitcoin_tx, + +# Negotiations complete, exiting. +msgtype,simpleclosed_complete,3004 diff --git a/common/close_tx.c b/common/close_tx.c index 4bd985bd5f1f..1c5dcc788882 100644 --- a/common/close_tx.c +++ b/common/close_tx.c @@ -4,6 +4,7 @@ #include #include #include +#include struct bitcoin_tx *create_close_tx(const tal_t *ctx, const struct chainparams *chainparams, @@ -91,3 +92,100 @@ struct bitcoin_tx *create_close_tx(const tal_t *ctx, assert(bitcoin_tx_check(tx)); return tx; } + +/* BOLT #3: + * + * ## Closing Transaction + * + * This variant is used for `closing_complete` and `closing_sig` messages + * (i.e. where `option_simple_close` is negotiated). + * ... + * The side with lesser funds can opt to omit their own output. + * * version: 2 + * * locktime: `locktime` from the `closing_complete` message + * * txin count: 1 + * * `txin[0]` outpoint: `txid` and `output_index` of the channel output + * * `txin[0]` sequence: 0xFFFFFFFD + * * `txin[0]` script bytes: 0 + * * `txin[0]` witness: `0 ` + */ +struct bitcoin_tx *create_simple_close_tx(const tal_t *ctx, + const struct chainparams *chainparams, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + const u8 *closer_script, + const u8 *closee_script, + const u8 *funding_wscript, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + struct amount_sat closer_amount, + struct amount_sat closee_amount, + u32 locktime) +{ + struct bitcoin_tx *tx; + size_t num_outputs = 0; + u8 *script; + + /* Sequence 0xFFFFFFFD signals RBF and satisfies the nSequence + * requirement for nLockTime. */ + tx = bitcoin_tx(ctx, chainparams, 1, 2, locktime); + + bitcoin_tx_add_input(tx, funding, + /* RBF-enabled, not final */ + 0xFFFFFFFD, + NULL, + funding_sats, NULL, funding_wscript); + + /* BOLT #3: + * * The closer output: * `txout` amount: + * * 0 if the `scriptpubkey` starts with `OP_RETURN` + * * otherwise the final balance for the closer, minus `closing_complete.fee_satoshis`, rounded down to whole satoshis + * * `txout` script: as specified in `closer_scriptpubkey` from the `closing_complete` message + */ + if (closer_script) { + struct amount_sat amt = closer_amount; + /* OP_RETURN output must have zero value */ + if (tal_count(closer_script) > 0 + && closer_script[0] == OP_RETURN) + amt = AMOUNT_SAT(0); + + script = tal_dup_talarr(tx, u8, closer_script); + bitcoin_tx_add_output(tx, script, NULL, amt); + assert((local_wallet_index == NULL) == (local_wallet_ext_key == NULL)); + if (local_wallet_index) { + size_t script_len = tal_bytelen(script); + if (!psbt_add_keypath_to_last_output( + tx, *local_wallet_index, local_wallet_ext_key, + is_p2tr(script, script_len, NULL))) + return tal_free(tx); + } + num_outputs++; + } + + /* BOLT #3: + * * The closee output: + * * `txout` amount: + * * 0 if the `scriptpubkey` starts with `OP_RETURN` + * * otherwise the final balance for the closee, rounded down to whole satoshis + * * `txout` script: as specified in `closee_scriptpubkey` from the `closing_complete` message + */ + if (closee_script) { + struct amount_sat amt = closee_amount; + if (tal_count(closee_script) > 0 + && closee_script[0] == OP_RETURN) + amt = AMOUNT_SAT(0); + + script = tal_dup_talarr(tx, u8, closee_script); + bitcoin_tx_add_output(tx, script, NULL, amt); + num_outputs++; + } + + if (num_outputs == 0) + return tal_free(tx); + + permute_outputs(tx, NULL, NULL); + + bitcoin_tx_finalize(tx); + assert(bitcoin_tx_check(tx)); + return tx; +} diff --git a/common/close_tx.h b/common/close_tx.h index 2c3aad61d4b6..e018e83c2b2f 100644 --- a/common/close_tx.h +++ b/common/close_tx.h @@ -19,4 +19,20 @@ struct bitcoin_tx *create_close_tx(const tal_t *ctx, struct amount_sat to_us, struct amount_sat to_them, struct amount_sat dust_limit); +/* Create simple close tx (option_simple_close) to spend the anchor tx output. + * The closer pays the fee; closee output is omitted if closee_script is NULL. + * Closer output is omitted if closer_script is NULL (or OP_RETURN with amount 0). + * Uses sequence 0xFFFFFFFD (RBF) and the specified locktime. */ +struct bitcoin_tx *create_simple_close_tx(const tal_t *ctx, + const struct chainparams *chainparams, + u32 *local_wallet_index, + const struct ext_key *local_wallet_ext_key, + const u8 *closer_script, + const u8 *closee_script, + const u8 *funding_wscript, + const struct bitcoin_outpoint *funding, + struct amount_sat funding_sats, + struct amount_sat closer_amount, + struct amount_sat closee_amount, + u32 locktime); #endif /* LIGHTNING_COMMON_CLOSE_TX_H */ diff --git a/common/features.c b/common/features.c index 7b6ed6cb5458..b112e0114ab3 100644 --- a/common/features.c +++ b/common/features.c @@ -140,6 +140,15 @@ static const struct feature_style feature_styles[] = { { OPT_PROVIDE_STORAGE, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT } }, + + /* option_simple_close (bits 60/61). + * Not in the default feature set yet; enabled via dev-force-features + * or when we're ready to advertise it unconditionally. + */ + { OPT_SIMPLE_CLOSE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, + [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, { OPT_SPLICE, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, diff --git a/common/features.h b/common/features.h index 2861573647ea..4d39b4ce4d47 100644 --- a/common/features.h +++ b/common/features.h @@ -125,35 +125,33 @@ struct feature_set *feature_set_dup(const tal_t *ctx, * | 50/51 | `option_zeroconf` | ... INT ... * | 60/61 | `option_simple_close` |... IN ... */ -/* BOLT-splice #9: - * | 62/63 | `option_splice` |... IN ... - */ #define OPT_DATA_LOSS_PROTECT 0 #define OPT_UPFRONT_SHUTDOWN_SCRIPT 4 -#define OPT_GOSSIP_QUERIES 6 -#define OPT_VAR_ONION 8 +#define OPT_GOSSIP_QUERIES 6 +#define OPT_VAR_ONION 8 #define OPT_GOSSIP_QUERIES_EX 10 #define OPT_STATIC_REMOTEKEY 12 -#define OPT_PAYMENT_SECRET 14 -#define OPT_BASIC_MPP 16 -#define OPT_LARGE_CHANNELS 18 +#define OPT_PAYMENT_SECRET 14 +#define OPT_BASIC_MPP 16 +#define OPT_LARGE_CHANNELS 18 /* FIXME: Update name to OPT_ANCHORS once old anchors is removed! */ -#define OPT_ANCHORS_ZERO_FEE_HTLC_TX 22 -#define OPT_ROUTE_BLINDING 24 +#define OPT_ANCHORS_ZERO_FEE_HTLC_TX 22 +#define OPT_ROUTE_BLINDING 24 #define OPT_SHUTDOWN_ANYSEGWIT 26 -#define OPT_DUAL_FUND 28 -#define OPT_QUIESCE 34 -#define OPT_ONION_MESSAGES 38 -#define OPT_PROVIDE_STORAGE 42 -#define OPT_CHANNEL_TYPE 44 -#define OPT_SCID_ALIAS 46 +#define OPT_DUAL_FUND 28 +#define OPT_QUIESCE 34 +#define OPT_ONION_MESSAGES 38 +#define OPT_PROVIDE_STORAGE 42 +#define OPT_CHANNEL_TYPE 44 +#define OPT_SCID_ALIAS 46 #define OPT_PAYMENT_METADATA 48 -#define OPT_ZEROCONF 50 -#define OPT_SPLICE 62 +#define OPT_ZEROCONF 50 +#define OPT_SIMPLE_CLOSE 60 +#define OPT_SPLICE 62 /* The old pre-zero-fee-anchors were deprecated, and we never supported them * outside experimental options */ -#define OPT_ANCHOR_OUTPUTS_DEPRECATED 20 +#define OPT_ANCHOR_OUTPUTS_DEPRECATED 20 #define OPT_SHUTDOWN_WRONG_FUNDING 104 diff --git a/lightningd/.gitignore b/lightningd/.gitignore index b2bb5c95f200..c39af0bd0d43 100644 --- a/lightningd/.gitignore +++ b/lightningd/.gitignore @@ -8,4 +8,5 @@ lightning_gossip_compactd lightning_hsmd lightning_onchaind lightning_openingd +lightning_simpleclosed lightning_websocketd diff --git a/lightningd/Makefile b/lightningd/Makefile index 4fb5afbc269c..ffbd5e624b27 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -42,6 +42,7 @@ LIGHTNINGD_SRC := \ lightningd/plugin_hook.c \ lightningd/routehint.c \ lightningd/runes.c \ + lightningd/simple_close_control.c \ lightningd/subd.c \ lightningd/wait.c \ lightningd/watch.c diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 55b8a381c266..c99e90a7ae61 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include struct stfu_result @@ -1387,6 +1390,7 @@ static void peer_start_closingd_after_shutdown(struct channel *channel, const int *fds) { struct peer_fd *peer_fd; + struct lightningd *ld = channel->peer->ld; if (!fromwire_channeld_shutdown_complete(msg)) { channel_internal_error(channel, "bad shutdown_complete: %s", @@ -1395,6 +1399,21 @@ static void peer_start_closingd_after_shutdown(struct channel *channel, } peer_fd = new_peer_fd_arr(msg, fds); + /* If both sides negotiated option_simple_close, use the simple close + * daemon instead of the legacy iterative fee negotiation daemon. */ + if (feature_negotiated(ld->our_features, + channel->peer->their_features, + OPT_SIMPLE_CLOSE)) { + peer_start_simpleclosed(channel, peer_fd); + if (channel->state == CHANNELD_SHUTTING_DOWN) + channel_set_state(channel, + CHANNELD_SHUTTING_DOWN, + CLOSINGD_SIGEXCHANGE, + REASON_UNKNOWN, + "Start simpleclosed"); + return; + } + /* This sets channel->owner, closes down channeld. */ peer_start_closingd(channel, peer_fd); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index ed00651814bc..bdc5fa059ffe 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -507,6 +508,35 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel, } } +/* Like drop_to_chain() for a cooperative close, but skips broadcasting the + * commitment tx. Used by simpleclosed: the closing txs have already been + * broadcast by the subdaemon, so we only need to (a) watch for the funding + * output being spent (so we can move to FUNDING_SPEND_SEEN when it confirms) + * and (b) resolve any pending close command. */ +void drop_to_chain_simple_close(struct lightningd *ld, struct channel *channel) +{ + /* Watch for the closing tx confirming on-chain. */ + if (!channel->funding_spend_watch) { + log_debug(channel->log, "Adding funding_spend_watch (simple close)"); + channel->funding_spend_watch = watch_txo(channel, + ld->topology, channel, + &channel->funding, + funding_spent); + } + + /* Record close attempt height for anchor rebroadcast logic. */ + if (channel->close_attempt_height == 0) { + channel->close_attempt_height = get_block_height(ld->topology); + wallet_channel_save(ld->wallet, channel); + } + + /* The closing txs were already broadcast by simpleclosed; resolve the + * pending close command with an empty txs array (no commitment tx to + * report — the mutual close txs were already submitted). */ + resolve_close_command(ld, channel, true, + tal_arr(tmpctx, const struct bitcoin_tx *, 0)); +} + void resend_closing_transactions(struct lightningd *ld) { struct peer *peer; @@ -532,7 +562,15 @@ void resend_closing_transactions(struct lightningd *ld) case CLOSED: continue; case CLOSINGD_COMPLETE: - drop_to_chain(ld, channel, true, NULL); + /* simple-close channels: the mutual close txs + * were already broadcast by simpleclosed; just + * re-watch (don't re-broadcast commit tx). */ + if (feature_negotiated(ld->our_features, + channel->peer->their_features, + OPT_SIMPLE_CLOSE)) + drop_to_chain_simple_close(ld, channel); + else + drop_to_chain(ld, channel, true, NULL); continue; case AWAITING_UNILATERAL: drop_to_chain(ld, channel, false, NULL); diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index 279a2f91d679..06a1958de32e 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -129,6 +129,12 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel, bool cooperative, const struct bitcoin_tx *unilateral_tx); +/* Variant for option_simple_close: the subdaemon already broadcast the mutual + * close txs, so we just watch for the funding spend and resolve the close + * command — without broadcasting the commitment tx. */ +void drop_to_chain_simple_close(struct lightningd *ld, + struct channel *channel); + 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 new file mode 100644 index 000000000000..df75172fe7e9 --- /dev/null +++ b/lightningd/simple_close_control.c @@ -0,0 +1,242 @@ +/* Master-side control for the simpleclosed subdaemon (option_simple_close). */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Master receives simpleclosed_got_sig: store tx and broadcast. */ +static void handle_simpleclosed_got_sig(struct channel *channel, const u8 *msg) +{ + struct lightningd *ld = channel->peer->ld; + struct bitcoin_tx *tx; + struct bitcoin_txid txid; + + if (!fromwire_simpleclosed_got_sig(tmpctx, msg, &tx)) { + channel_internal_error(channel, "bad simpleclosed_got_sig: %s", + tal_hex(msg, msg)); + return; + } + tx->chainparams = chainparams; + + /* Broadcast it (rebroadcast on restarts as needed). */ + broadcast_tx(channel, ld->topology, channel, tx, NULL, false, 0, + NULL, NULL, NULL); + + bitcoin_txid(tx, &txid); + log_info(channel->log, + "Simple close: broadcasting closer tx %s", + fmt_bitcoin_txid(tmpctx, &txid)); + + subd_send_msg(channel->owner, + take(towire_simpleclosed_got_sig_reply(NULL, &txid))); +} + +/* Master receives simpleclosed_closee_broadcast: broadcast peer's closing tx + * that we signed as the closee. */ +static void handle_simpleclosed_closee_broadcast(struct channel *channel, + const u8 *msg) +{ + struct lightningd *ld = channel->peer->ld; + struct bitcoin_tx *tx; + struct bitcoin_txid txid; + + if (!fromwire_simpleclosed_closee_broadcast(tmpctx, msg, &tx)) { + channel_internal_error(channel, + "bad simpleclosed_closee_broadcast: %s", + tal_hex(msg, msg)); + return; + } + tx->chainparams = chainparams; + + broadcast_tx(channel, ld->topology, channel, tx, NULL, false, 0, + NULL, NULL, NULL); + + bitcoin_txid(tx, &txid); + log_info(channel->log, + "Simple close: broadcasting closee tx %s", + fmt_bitcoin_txid(tmpctx, &txid)); +} + +static void handle_simpleclosed_complete(struct channel *channel, const u8 *msg) +{ + if (!fromwire_simpleclosed_complete(msg)) { + channel_internal_error(channel, + "bad simpleclosed_complete: %s", + tal_hex(msg, msg)); + return; + } + + /* Don't report spurious failure when simpleclosed exits. */ + channel_set_owner(channel, NULL); + channel_set_billboard(channel, false, NULL); + + /* Retransmission only, ignore. */ + if (channel->state != CLOSINGD_SIGEXCHANGE) + return; + + channel_set_state(channel, + CLOSINGD_SIGEXCHANGE, + CLOSINGD_COMPLETE, + REASON_UNKNOWN, + "Simple close complete"); + + /* Watch for the closing tx confirming on-chain and resolve any pending + * close command. We use drop_to_chain_simple_close() rather than + * drop_to_chain() to avoid broadcasting the commitment tx: the mutual + * close txs have already been submitted by the subdaemon, and + * broadcasting the commitment tx would race with them (and potentially + * replace them via RBF, causing onchaind to treat the close as + * unilateral). */ + drop_to_chain_simple_close(channel->peer->ld, channel); +} + +static unsigned int simpleclosed_msg(struct subd *sd, const u8 *msg, + const int *fds UNUSED) +{ + enum simpleclosed_wire t = fromwire_peektype(msg); + + switch (t) { + case WIRE_SIMPLECLOSED_GOT_SIG: + handle_simpleclosed_got_sig(sd->channel, msg); + return 0; + case WIRE_SIMPLECLOSED_CLOSEE_BROADCAST: + handle_simpleclosed_closee_broadcast(sd->channel, msg); + return 0; + case WIRE_SIMPLECLOSED_COMPLETE: + handle_simpleclosed_complete(sd->channel, msg); + return 0; + + /* Inbound-only (master→daemon) — should not be received here. */ + case WIRE_SIMPLECLOSED_INIT: + case WIRE_SIMPLECLOSED_GOT_SIG_REPLY: + break; + } + + return 0; +} + +void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd) +{ + u8 *initmsg; + u32 min_feerate, max_feerate; + struct amount_msat their_msat; + int hsmfd; + struct lightningd *ld = channel->peer->ld; + u32 *local_wallet_index = NULL; + struct ext_key *local_wallet_ext_key = NULL; + u32 index_val; + struct ext_key ext_key_val; + + if (!channel->shutdown_scriptpubkey[REMOTE]) { + channel_internal_error(channel, + "Can't start simpleclosed: no remote script"); + return; + } + + hsmfd = hsm_get_client_fd(ld, &channel->peer->id, channel->dbid, + HSM_PERM_SIGN_CLOSING_TX + | HSM_PERM_COMMITMENT_POINT); + if (hsmfd < 0) { + log_broken(channel->log, + "Could not get hsm fd for simpleclosed: %s", + strerror(errno)); + force_peer_disconnect(ld, channel->peer, + "Failed to get hsm fd for simpleclosed"); + return; + } + + channel_set_owner(channel, + new_channel_subd(channel, ld, + "lightning_simpleclosed", + channel, &channel->peer->id, + channel->log, true, + simpleclosed_wire_name, + simpleclosed_msg, + channel_errmsg, + channel_set_billboard, + take(&peer_fd->fd), + take(&hsmfd), + NULL)); + + if (!channel->owner) { + log_broken(channel->log, + "Could not subdaemon simpleclosed: %s", + strerror(errno)); + force_peer_disconnect(ld, channel->peer, + "Failed to create simpleclosed"); + return; + } + + /* Compute their balance. */ + if (!amount_sat_sub_msat(&their_msat, + channel->funding_sats, channel->our_msat)) { + log_broken(channel->log, + "our_msat overflow on simple close: %s minus %s", + fmt_amount_sat(tmpctx, channel->funding_sats), + fmt_amount_msat(tmpctx, channel->our_msat)); + channel_fail_permanent(channel, REASON_LOCAL, + "our_msat overflow on simple close"); + return; + } + + min_feerate = feerate_min(ld, NULL); + max_feerate = unilateral_feerate(ld->topology, false); + if (!max_feerate) + max_feerate = get_feerate(channel->fee_states, + channel->opener, LOCAL); + + /* Wallet key for our output. */ + if (wallet_can_spend(ld->wallet, + channel->shutdown_scriptpubkey[LOCAL], + tal_bytelen(channel->shutdown_scriptpubkey[LOCAL]), + &index_val, NULL)) { + if (bip32_key_from_parent(ld->bip32_base, index_val, + BIP32_FLAG_KEY_PUBLIC, + &ext_key_val) != WALLY_OK) { + channel_internal_error(channel, + "Could not derive ext public key"); + return; + } + local_wallet_index = &index_val; + local_wallet_ext_key = &ext_key_val; + } + + initmsg = towire_simpleclosed_init(tmpctx, + chainparams, + &channel->cid, + &channel->funding, + channel->funding_sats, + &channel->local_funding_pubkey, + &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, + min_feerate, + max_feerate, + local_wallet_index, + local_wallet_ext_key, + channel->shutdown_scriptpubkey[LOCAL], + channel->shutdown_scriptpubkey[REMOTE], + channel->opener); + + subd_send_msg(channel->owner, take(initmsg)); +} diff --git a/lightningd/simple_close_control.h b/lightningd/simple_close_control.h new file mode 100644 index 000000000000..524ac3bc2a81 --- /dev/null +++ b/lightningd/simple_close_control.h @@ -0,0 +1,11 @@ +#ifndef LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H +#define LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H +#include "config.h" + +struct channel; +struct peer_fd; + +/* Start the simpleclosed subdaemon for option_simple_close negotiation. */ +void peer_start_simpleclosed(struct channel *channel, struct peer_fd *peer_fd); + +#endif /* LIGHTNING_LIGHTNINGD_SIMPLE_CLOSE_CONTROL_H */ diff --git a/tests/test_closing.py b/tests/test_closing.py index 9dec96d7f92e..b6fe1ec48d61 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -4114,6 +4114,191 @@ def test_closing_cpfp(node_factory, bitcoind): sync_blockheight(bitcoind, [l1, l2]) assert len(l1.rpc.listfunds()['outputs']) == 2 +# --------------------------------------------------------------------------- +# option_simple_close (BOLT #2 §closing_complete / closing_sig) +# +# OPT_SIMPLE_CLOSE (bit 60) is not yet in the default feature set, so these +# tests force it on both nodes with --dev-force-features. +# --------------------------------------------------------------------------- + + +@pytest.mark.developer("needs dev-force-features to enable OPT_SIMPLE_CLOSE") +def test_simple_close_basic(node_factory, bitcoind, chainparams): + """Happy path: both nodes negotiate option_simple_close, fund a channel, + make a payment, then close cooperatively. Each side independently builds + and broadcasts its own closing tx; both spend the funding output so only + one can confirm.""" + opts = {'dev-force-features': '+60'} + l1, l2 = node_factory.line_graph(2, opts=opts) + chan = l1.get_channel_scid(l2) + + l1.pay(l2, 200000000) + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + l1.rpc.close(chan) + + l1.daemon.wait_for_log(' to CHANNELD_SHUTTING_DOWN') + l2.daemon.wait_for_log(' to CHANNELD_SHUTTING_DOWN') + + l1.daemon.wait_for_log(' to CLOSINGD_SIGEXCHANGE') + l2.daemon.wait_for_log(' to CLOSINGD_SIGEXCHANGE') + + # Verify the simpleclosed daemon (not legacy closingd) handled the exchange. + l1.daemon.wait_for_log('Simple close starting') + l2.daemon.wait_for_log('Simple close starting') + + # Each side broadcasts its own version of the closing tx. + l1.daemon.wait_for_log('Simple close: broadcasting') + l2.daemon.wait_for_log('Simple close: broadcasting') + l1.daemon.wait_for_log('sendrawtx exit 0') + l2.daemon.wait_for_log('sendrawtx exit 0') + + # Each node builds its own closing tx; both spend the same funding output so + # only one can be in the mempool at a time (the other is rejected as an + # insufficient-fee RBF replacement at equal feerate). + wait_for(lambda: bitcoind.rpc.getmempoolinfo()['size'] == 1) + + # Mine one block: the winner confirms, the loser is evicted. + bitcoind.generate_block(1) + confirmed_txid = bitcoind.rpc.getblock( + bitcoind.rpc.getbestblockhash())['tx'][1] + + # Both nodes must claim their output from whichever tx won. + outtype = 'p2tr' if not chainparams['elements'] else 'p2wpkh' + l1.daemon.wait_for_log( + rf'Owning output.* \({outtype}\).* txid {confirmed_txid}.* CONFIRMED') + l2.daemon.wait_for_log( + rf'Owning output.* \({outtype}\).* txid {confirmed_txid}.* CONFIRMED') + + wait_for(lambda: confirmed_txid in + {o['txid'] for o in l1.rpc.listfunds()['outputs']}) + wait_for(lambda: confirmed_txid in + {o['txid'] for o in l2.rpc.listfunds()['outputs']}) + + +@pytest.mark.developer("needs dev-force-features to enable OPT_SIMPLE_CLOSE") +def test_simple_close_closer_pays_fee(node_factory, bitcoind): + """The closing node (the closer) pays the on-chain fee; the closee gets + its exact channel balance as an output with no deduction.""" + opts = {'dev-force-features': '+60', 'feerates': (3750, 3750, 3750, 3750, 3750)} + l1, l2 = node_factory.line_graph(2, opts=opts) + chan = l1.get_channel_scid(l2) + + l1.pay(l2, 200000000) + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + # Sample both balances (in sat) before close. + l2_bal = only_one( + l2.rpc.listpeerchannels(l1.info['id'])['channels'])['to_us_msat'] // 1000 + l1_bal = only_one( + l1.rpc.listpeerchannels(l2.info['id'])['channels'])['to_us_msat'] // 1000 + + # l1 initiates: l1 is the closer and bears the fee. + l1.rpc.close(chan) + l1.daemon.wait_for_log('Simple close starting') + l1.daemon.wait_for_log('sendrawtx exit 0') + l2.daemon.wait_for_log('sendrawtx exit 0') + + # SIMPLE_CLOSE_WEIGHT = 900 wu (defined in simpleclosed.c). + expected_fee = 3750 * 900 // 1000 # = 3375 sat + + # Only one of the two conflicting closing txs can be in the mempool at a time. + wait_for(lambda: bitcoind.rpc.getmempoolinfo()['size'] == 1) + + # Inspect whichever tx won the race. The invariant: one output equals the + # closee's exact balance (no fee deducted) and the other equals the closer's + # balance minus the fee. Either (l1-closer, l2-closee) or the reverse is fine. + txid = only_one(bitcoind.rpc.getrawmempool()) + tx = bitcoind.rpc.getrawtransaction(txid, True) + out_sats = sorted(int(round(v['value'] * 10**8)) for v in tx['vout']) + assert len(out_sats) == 2, f"Expected 2 outputs in closing tx, got {out_sats}" + + if l2_bal in out_sats: + # l1 was the closer in this tx: l2 (closee) gets exact balance. + l1_out = [s for s in out_sats if s != l2_bal][0] + assert l1_out == l1_bal - expected_fee, \ + f"l1 closer output {l1_out} sat != {l1_bal} - {expected_fee} = {l1_bal - expected_fee}" + elif l1_bal in out_sats: + # l2 was the closer in this tx: l1 (closee) gets exact balance. + l2_out = [s for s in out_sats if s != l1_bal][0] + assert l2_out == l2_bal - expected_fee, \ + f"l2 closer output {l2_out} sat != {l2_bal} - {expected_fee} = {l2_bal - expected_fee}" + else: + raise AssertionError( + f"Neither l1_bal ({l1_bal} sat) nor l2_bal ({l2_bal} sat) " + f"found as a full (undeducted) output in closing tx; outputs={out_sats}" + ) + + +@pytest.mark.developer("needs dev-force-features to enable OPT_SIMPLE_CLOSE") +@pytest.mark.skip("need to fix closingd/simplecosed.c:609") +def test_simple_close_dust_output_omitted(node_factory, bitcoind): + """When the closee's output would be below the dust limit it must be + omitted from the closing tx (closer_output_only TLV variant).""" + opts = {'dev-force-features': '+60', 'feerates': (3750, 3750, 3750, 3750, 3750)} + l1, l2 = node_factory.line_graph(2, opts=opts) + chan = l1.get_channel_scid(l2) + + # Give l2 a balance well below the default 546-sat dust limit. + l1.pay(l2, 400000) # 400000 msat = 400 sat + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + l2_bal = only_one( + l2.rpc.listpeerchannels(l1.info['id'])['channels'])['to_us_msat'] // 1000 + assert l2_bal < 546, f"l2 balance {l2_bal} sat must be below dust limit for this test" + + # l1 is the non-lesser side (l1 >> l2), so it sends closer_output_only + # because the closee (l2) is dust; l2 as closer also has a dust-sized output + # after subtracting the fee (400 sat balance, fee 3375 sat → capped at 400 sat). + l1.rpc.close(chan) + l1.daemon.wait_for_log('Simple close starting') + l1.daemon.wait_for_log('sendrawtx exit 0') + + wait_for(lambda: bitcoind.rpc.getmempoolinfo()['size'] >= 1) + + # Every closing tx in the mempool must have exactly 1 output: the dust + # output is omitted in all variants. + for txid in bitcoind.rpc.getrawmempool(): + tx = bitcoind.rpc.getrawtransaction(txid, True) + assert len(tx['vout']) == 1, \ + f"tx {txid} has {len(tx['vout'])} outputs; expected 1 (dust omitted)" + + +@pytest.mark.developer("needs dev-force-features to disable OPT_SIMPLE_CLOSE") +def test_simple_close_no_feature_fallback(node_factory, bitcoind, chainparams): + """Without option_simple_close the nodes must fall back to legacy closingd + (iterative closing_signed fee negotiation) and produce a single mutually- + agreed closing tx.""" + # Explicitly remove bit 60 to guard against future default-on changes. + opts = {'dev-force-features': '-60'} + l1, l2 = node_factory.line_graph(2, opts=opts) + chan = l1.get_channel_scid(l2) + fee = closing_fee(3750, 2) if not chainparams['elements'] else 4278 + + l1.pay(l2, 200000000) + wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + l1.rpc.close(chan) + + l1.daemon.wait_for_log(' to CHANNELD_SHUTTING_DOWN') + l1.daemon.wait_for_log(' to CLOSINGD_SIGEXCHANGE') + + # No simpleclosed daemon should have been started. + assert not l1.daemon.is_in_log('Simple close starting') + + l1.daemon.wait_for_log('sendrawtx exit 0') + + # Legacy mutual close: both sides agree on one tx, not two. + assert bitcoind.rpc.getmempoolinfo()['size'] == 1 + + closetxid = only_one(bitcoind.rpc.getrawmempool(False)) + billboard = only_one( + l1.rpc.listpeerchannels(l2.info['id'])['channels'])['status'] + assert billboard == [ + 'CLOSINGD_SIGEXCHANGE:We agreed on a closing fee of ' + '{} satoshi for tx:{}'.format(fee, closetxid), + ] + @pytest.mark.skip("Solely to generate the blockchain and test dbs, before we fixed output p2pkh watching") def test_onchain_p2tr_missed_txs(node_factory, bitcoind):