diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 9049beec3..5dc11f4e7 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -3636,7 +3636,8 @@ bool peer_start_dualopend(struct peer *peer, min_effective_htlc_capacity, &channel->local_basepoints, &channel->local_funding_pubkey, - channel->minimum_depth); + channel->minimum_depth, + peer->ld->config.require_confirmed_inputs); subd_send_msg(channel->owner, take(msg)); return true; } @@ -3744,7 +3745,8 @@ bool peer_restart_dualopend(struct peer *peer, inflight->lease_chan_max_ppt, amount_sat_zero(inflight->lease_amt) ? NULL : &inflight->lease_amt, - channel->type); + channel->type, + false); /* FIXME: use persisted state? */ subd_send_msg(channel->owner, take(msg)); return true; diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 7bc6e59f6..c9bc6b094 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -218,7 +218,7 @@ struct state { struct amount_sat *requested_lease; /* Does this negotation require confirmed inputs? */ - bool require_confirmed_inputs; + bool require_confirmed_inputs[NUM_SIDES]; }; /* psbt_changeset_get_next - Get next message to send @@ -517,6 +517,35 @@ static bool is_dust(struct tx_state *tx_state, || !amount_sat_greater(amount, tx_state->remoteconf.dust_limit); } +static char *validate_inputs(struct state *state, + struct tx_state *tx_state, + enum tx_role role_to_validate) +{ + /* BOLT-18195c86294f503ffd2f11563250c854a50bfa51 #2: + * Upon receipt of consecutive `tx_complete`s, the receiving node: + * ... + * - if it has sent `require_confirmed_inputs` in `open_channel2` + * or `accept_channel2`: + * - MUST fail the negotiation if: + * - one of the inputs added by the other peer is unconfirmed + */ + u8 *msg; + char *err_reason; + + msg = towire_dualopend_validate_inputs(NULL, tx_state->psbt, + role_to_validate); + wire_sync_write(REQ_FD, take(msg)); + msg = wire_sync_read(tmpctx, REQ_FD); + + if (!fromwire_dualopend_validate_inputs_reply(msg)) { + if (!fromwire_dualopend_fail(tmpctx, msg, &err_reason)) + master_badmsg(fromwire_peektype(msg), msg); + return err_reason; + } + + return NULL; +} + static void set_reserve(struct tx_state *tx_state, struct amount_sat funding_total, enum tx_role our_role) @@ -1136,7 +1165,7 @@ fetch_psbt_changes(struct state *state, /* Go ask lightningd what other changes we've got */ msg = towire_dualopend_psbt_changed(NULL, &state->channel_id, - state->require_confirmed_inputs, + state->require_confirmed_inputs[REMOTE], tx_state->funding_serial, psbt); @@ -2207,7 +2236,8 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) open_err_fatal(state, "Parsing open_channel2 %s", tal_hex(tmpctx, oc2_msg)); - state->require_confirmed_inputs = open_tlv->require_confirmed_inputs != NULL; + state->require_confirmed_inputs[REMOTE] = + open_tlv->require_confirmed_inputs != NULL; if (open_tlv->upfront_shutdown_script) set_remote_upfront_shutdown(state, open_tlv->upfront_shutdown_script); @@ -2326,7 +2356,7 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) state->upfront_shutdown_script[REMOTE], state->requested_lease, tx_state->blockheight, - state->require_confirmed_inputs); + state->require_confirmed_inputs[REMOTE]); wire_sync_write(REQ_FD, take(msg)); msg = wire_sync_read(tmpctx, REQ_FD); @@ -2493,6 +2523,16 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) state->our_funding_pubkey, tx_state->blockheight); + /* BOLT-18195c86294f503ffd2f11563250c854a50bfa51 #2: + * + * The sending node may require the other participant to + * only use confirmed inputs. This ensures that the sending + * node doesn't end up paying the fees of a low feerate + * unconfirmed ancestor of one of the other participant's inputs. + */ + if (state->require_confirmed_inputs[LOCAL]) + a_tlv->require_confirmed_inputs = + tal(a_tlv, struct tlv_accept_tlvs_require_confirmed_inputs); msg = towire_accept_channel2(tmpctx, &state->channel_id, /* Our amount w/o the lease fee */ @@ -2527,6 +2567,14 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) if (!run_tx_interactive(state, tx_state, &tx_state->psbt, TX_ACCEPTER)) return; + if (state->require_confirmed_inputs[LOCAL]) { + err_reason = validate_inputs(state, tx_state, TX_INITIATOR); + if (err_reason) { + open_abort(state, "%s", err_reason); + return; + } + } + msg = accepter_commits(state, tx_state, total, &err_reason); if (!msg) { if (err_reason) @@ -2927,6 +2975,17 @@ static void opener_start(struct state *state, u8 *msg) open_tlv->request_funds->blockheight = tx_state->blockheight; } + /* BOLT-18195c86294f503ffd2f11563250c854a50bfa51 #2: + * + * The sending node may require the other participant to + * only use confirmed inputs. This ensures that the sending + * node doesn't end up paying the fees of a low feerate + * unconfirmed ancestor of one of the other participant's inputs. + */ + if (state->require_confirmed_inputs[LOCAL]) + open_tlv->require_confirmed_inputs = + tal(open_tlv, struct tlv_opening_tlvs_require_confirmed_inputs); + msg = towire_open_channel2(NULL, &chainparams->genesis_blockhash, &state->channel_id, @@ -2997,7 +3056,8 @@ static void opener_start(struct state *state, u8 *msg) } /* Set the require confirms from peer's TLVs */ - state->require_confirmed_inputs = a_tlv->require_confirmed_inputs != NULL; + state->require_confirmed_inputs[REMOTE] = + a_tlv->require_confirmed_inputs != NULL; if (a_tlv->upfront_shutdown_script) set_remote_upfront_shutdown(state, a_tlv->upfront_shutdown_script); @@ -3015,7 +3075,7 @@ static void opener_start(struct state *state, u8 *msg) msg = towire_dualopend_dry_run(NULL, &state->channel_id, tx_state->opener_funding, tx_state->accepter_funding, - state->require_confirmed_inputs, + state->require_confirmed_inputs[REMOTE], a_tlv->will_fund ? &a_tlv->will_fund->lease_rates : NULL); @@ -3185,16 +3245,9 @@ static void opener_start(struct state *state, u8 *msg) /* We need to check that the inputs we've already provided * via the API are confirmed :/ */ - if (state->require_confirmed_inputs) { - msg = towire_dualopend_validate_inputs(NULL, tx_state->psbt, - state->our_role); - wire_sync_write(REQ_FD, take(msg)); - msg = wire_sync_read(tmpctx, REQ_FD); - - if (!fromwire_dualopend_validate_inputs_reply(msg)) { - if (!fromwire_dualopend_fail(msg, msg, &err_reason)) - master_badmsg(fromwire_peektype(msg), msg); - /* We abort, because we don't have valid inputs */ + if (state->require_confirmed_inputs[REMOTE]) { + err_reason = validate_inputs(state, tx_state, state->our_role); + if (err_reason) { open_abort(state, "%s", err_reason); return; } @@ -3239,6 +3292,15 @@ static void opener_start(struct state *state, u8 *msg) if (!run_tx_interactive(state, tx_state, &tx_state->psbt, TX_INITIATOR)) return; + if (state->require_confirmed_inputs[LOCAL]) { + err_reason = validate_inputs(state, tx_state, TX_ACCEPTER); + if (err_reason) { + open_abort(state, "%s", err_reason); + return; + } + } + + msg = opener_commits(state, tx_state, total, &err_reason); if (!msg) { if (err_reason) @@ -3318,6 +3380,16 @@ static void rbf_wrap_up(struct state *state, return; } + if (state->require_confirmed_inputs[LOCAL]) { + err_reason = validate_inputs(state, tx_state, + state->our_role == TX_INITIATOR ? + TX_ACCEPTER : TX_INITIATOR); + if (err_reason) { + open_abort(state, "%s", err_reason); + return; + } + } + /* Is this an eligible RBF (at least one overlapping input) */ msg = towire_dualopend_rbf_validate(NULL, tx_state->psbt); wire_sync_write(REQ_FD, take(msg)); @@ -4156,7 +4228,8 @@ int main(int argc, char *argv[]) &state->min_effective_htlc_capacity, &state->our_points, &state->our_funding_pubkey, - &state->minimum_depth)) { + &state->minimum_depth, + &state->require_confirmed_inputs[LOCAL])) { /*~ Initially we're not associated with a channel, but * handle_peer_gossip_or_error compares this. */ memset(&state->channel_id, 0, sizeof(state->channel_id)); @@ -4215,7 +4288,8 @@ int main(int argc, char *argv[]) &state->tx_state->lease_chan_max_msat, &state->tx_state->lease_chan_max_ppt, &requested_lease, - &state->channel_type)) { + &state->channel_type, + &state->require_confirmed_inputs[LOCAL])) { bool ok; diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index c38f096b4..a02fd33e3 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -28,6 +28,7 @@ msgdata,dualopend_init,our_basepoints,basepoints, msgdata,dualopend_init,our_funding_pubkey,pubkey, # Constraints in case the other end tries to open a channel. msgdata,dualopend_init,minimum_depth,u32, +msgdata,dualopend_init,require_confirmed_inputs,bool, # master-dualopend: peer has reconnected msgtype,dualopend_reinit,7001 @@ -71,6 +72,7 @@ msgdata,dualopend_reinit,lease_chan_max_msat,u32, msgdata,dualopend_reinit,lease_chan_max_ppt,u16, msgdata,dualopend_reinit,requested_lease,?amount_sat, msgdata,dualopend_reinit,channel_type,channel_type, +msgdata,dualopend_reinit,we_require_confirmed_inputs,bool, # dualopend->master: they offered channel, should we continue? msgtype,dualopend_got_offer,7005