From 6474779e38e9ff4fbcee3869a401b4c97f54b186 Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 13 Jan 2021 19:54:14 -0600 Subject: [PATCH] df-rbf: hook for rbf_init attempt, called "rbf_channel" When we get an RBF request, we ask the/a plugin what they'd like to do about it. This pipes the request through to the plugin --- lightningd/dual_open_control.c | 222 ++++++++++++++++++++++++++++++++- openingd/dualopend_wire.csv | 4 +- openingd/dualopend_wiregen.c | 18 +-- openingd/dualopend_wiregen.h | 10 +- 4 files changed, 237 insertions(+), 17 deletions(-) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index fc8b01e55..8ceb4b8ef 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -55,6 +55,45 @@ static void handle_signed_psbt(struct lightningd *ld, take(towire_dualopend_send_tx_sigs(NULL, psbt))); } +struct rbf_channel_payload { + struct subd *dualopend; + struct node_id peer_id; + + /* Info specific to this RBF */ + struct channel_id channel_id; + struct amount_sat their_funding; + u32 funding_feerate_per_kw; + u32 locktime; + + /* General info */ + u32 feerate_our_max; + u32 feerate_our_min; + + /* Returned from hook */ + struct amount_sat our_funding; + struct wally_psbt *psbt; + char *err_msg; +}; + +static void +rbf_channel_hook_serialize(struct rbf_channel_payload *payload, + struct json_stream *stream) +{ + json_object_start(stream, "rbf_channel"); + json_add_node_id(stream, "id", &payload->peer_id); + json_add_channel_id(stream, "channel_id", &payload->channel_id); + json_add_amount_sat_only(stream, "their_funding", + payload->their_funding); + json_add_num(stream, "locktime", payload->locktime); + json_add_num(stream, "feerate_our_max", + payload->feerate_our_max); + json_add_num(stream, "feerate_our_min", + payload->feerate_our_min); + json_add_num(stream, "funding_feerate_per_kw", + payload->funding_feerate_per_kw); + json_object_end(stream); +} + /* ~Map of the Territory~ * * openchannel hook @@ -348,6 +387,141 @@ static bool psbt_side_contribs_changed(struct wally_psbt *orig, return false; } +static void rbf_channel_remove_dualopend(struct subd *dualopend, + struct rbf_channel_payload *payload) +{ + assert(payload->dualopend == dualopend); + payload->dualopend = NULL; +} + +static void rbf_channel_hook_cb(struct rbf_channel_payload *payload STEALS) +{ + struct subd *dualopend = payload->dualopend; + struct channel *channel; + u8 *msg; + + tal_steal(tmpctx, payload); + + if (!dualopend) + return; + + assert(dualopend->ctype == CHANNEL); + channel = dualopend->channel; + + tal_del_destructor2(dualopend, rbf_channel_remove_dualopend, payload); + + if (channel->state != DUALOPEND_AWAITING_LOCKIN) { + log_debug(channel->log, + "rbf_channel hook returned, but channel in state" + " %s", channel_state_name(channel)); + msg = towire_dualopend_fail(NULL, "Peer error. Channel" + " not ready for RBF attempt."); + return subd_send_msg(dualopend, take(msg)); + } + + if (payload->err_msg) { + log_debug(channel->log, + "rbf_channel hook rejects and says '%s'", + payload->err_msg); + msg = towire_dualopend_fail(NULL, payload->err_msg); + return subd_send_msg(dualopend, take(msg)); + } + + /* Update channel info for "inflight" attempt. + * Note that an 'inflight' doesn't get created + * until *after* we've completed the tx_sigs exchange */ + // FIXME: reinstitute the uc?? but where?? + //struct uncommitted_channel *uc; + msg = towire_dualopend_got_rbf_offer_reply(NULL, + payload->our_funding, + payload->psbt); + subd_send_msg(dualopend, take(msg)); +} + + + +static bool +rbf_channel_hook_deserialize(struct rbf_channel_payload *payload, + const char *buffer, + const jsmntok_t *toks) +{ + struct subd *dualopend = payload->dualopend; + struct channel *channel; + + if (!dualopend) { + tal_free(dualopend); + return false; + } + + assert(dualopend->ctype == CHANNEL); + channel = dualopend->channel; + + /* FIXME: move to new json extraction */ + const jsmntok_t *t_result = json_get_member(buffer, toks, "result"); + if (!t_result) + fatal("Plugin returned an invalid response to the" + " rbf_channel hook: %.*s", + json_tok_full_len(toks), + json_tok_full(buffer, toks)); + + if (json_tok_streq(buffer, t_result, "reject")) { + if (json_get_member(buffer, toks, "psbt")) + fatal("Plugin rejected rbf_channel but" + " also set `psbt`"); + if (json_get_member(buffer, toks, "our_funding_msat")) + fatal("Plugin rejected rbf_channel but" + " also set `our_funding_msat`"); + + const jsmntok_t *t_errmsg = json_get_member(buffer, toks, + "error_message"); + if (t_errmsg) + payload->err_msg = json_strdup(payload, buffer, + t_errmsg); + else + payload->err_msg = ""; + + rbf_channel_hook_cb(payload); + return false; + } else if (!json_tok_streq(buffer, t_result, "continue")) + fatal("Plugin returned invalid response to rbf_channel hook:" + " %.*s", json_tok_full_len(toks), + json_tok_full(buffer, toks)); + + if (!hook_extract_psbt(payload, dualopend, buffer, toks, + "rbf_channel", true, &payload->psbt)) + return false; + + if (payload->psbt) { + enum tx_role our_role = channel->opener == LOCAL ? + TX_INITIATOR : TX_ACCEPTER; + psbt_add_serials(payload->psbt, our_role); + } + + if (payload->psbt && !psbt_has_required_fields(payload->psbt)) + fatal("Plugin supplied PSBT that's missing" + " required fields: %s", + type_to_string(tmpctx, struct wally_psbt, + payload->psbt)); + if (!hook_extract_amount(dualopend, buffer, toks, + "our_funding_msat", &payload->our_funding)) + fatal("Plugin failed to supply our_funding_msat field"); + + if (!payload->psbt && + !amount_sat_eq(payload->our_funding, AMOUNT_SAT(0))) { + + log_broken(channel->log, "`our_funding_msat` returned" + " but no `psbt` present. %.*s", + json_tok_full_len(toks), + json_tok_full(buffer, toks)); + + payload->err_msg = "Client error. Unable to continue"; + rbf_channel_hook_cb(payload); + return false; + } + + return true; +} + /* dualopend dies? Remove dualopend ptr from payload */ static void openchannel2_remove_dualopend(struct subd *dualopend, struct openchannel2_payload *payload) @@ -671,6 +845,12 @@ REGISTER_PLUGIN_HOOK(openchannel2_sign, openchannel2_sign_hook_serialize, struct openchannel2_psbt_payload *); +REGISTER_PLUGIN_HOOK(rbf_channel, + rbf_channel_hook_deserialize, + rbf_channel_hook_cb, + rbf_channel_hook_serialize, + struct rbf_channel_payload *); + /* Steals fields from uncommitted_channel: returns NULL if can't generate a * key for this channel (shouldn't happen!). */ static struct channel * @@ -1466,6 +1646,46 @@ static void accepter_psbt_changed(struct subd *dualopend, plugin_hook_call_openchannel2_changed(dualopend->ld, payload); } +static void rbf_got_offer(struct subd *dualopend, const u8 *msg) +{ + /* We expect the channel to still exist?! */ + struct channel *channel; + struct rbf_channel_payload *payload; + + assert(dualopend->ctype == CHANNEL); + channel = dualopend->channel; + + payload = tal(dualopend, struct rbf_channel_payload); + payload->dualopend = dualopend; + + if (!fromwire_dualopend_got_rbf_offer(msg, + &payload->channel_id, + &payload->their_funding, + &payload->funding_feerate_per_kw, + &payload->locktime)) { + log_broken(channel->log, "Malformed dualopend_got_rbf_offer %s", + tal_hex(msg, msg)); + // FIXME: is this the right thing to do here? + tal_free(dualopend); + return; + } + + /* Fill in general channel info from channel */ + payload->peer_id = channel->peer->id; + payload->feerate_our_max = feerate_max(dualopend->ld, NULL); + payload->feerate_our_min = feerate_min(dualopend->ld, NULL); + + /* Set our contributions to empty, in case there is no plugin */ + payload->our_funding = AMOUNT_SAT(0); + payload->psbt = NULL; + + /* No error message known (yet) */ + payload->err_msg = NULL; + + tal_add_destructor2(dualopend, rbf_channel_remove_dualopend, payload); + plugin_hook_call_rbf_channel(dualopend->ld, payload); +} + static void accepter_got_offer(struct subd *dualopend, struct uncommitted_channel *uc, const u8 *msg) @@ -2002,7 +2222,7 @@ static unsigned int dual_opend_msg(struct subd *dualopend, accepter_got_offer(dualopend, uc, msg); return 0; case WIRE_DUALOPEND_GOT_RBF_OFFER: - // FIXME: do this + rbf_got_offer(dualopend, msg); return 0; case WIRE_DUALOPEND_PSBT_CHANGED: if (uc->fc) { diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index 49855e84e..b19150898 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -102,13 +102,13 @@ msgdata,dualopend_got_offer_reply,our_shutdown_scriptpubkey,?u8,shutdown_len # dualopend->master: they offered a RBF, should we continue? msgtype,dualopend_got_rbf_offer,7500 msgdata,dualopend_got_rbf_offer,channel_id,channel_id, -msgdata,dualopend_got_rbf_offer,opener_funding,amount_sat, +msgdata,dualopend_got_rbf_offer,their_funding,amount_sat, msgdata,dualopend_got_rbf_offer,funding_feerate_per_kw,u32, msgdata,dualopend_got_rbf_offer,locktime,u32, # master->dualopend: reply back with our funding info/contribs msgtype,dualopend_got_rbf_offer_reply,7505 -msgdata,dualopend_got_rbf_offer_reply,accepter_funding,amount_sat, +msgdata,dualopend_got_rbf_offer_reply,our_funding,amount_sat, msgdata,dualopend_got_rbf_offer_reply,psbt,wally_psbt, # dualopend->master: is this a valid RBF candidate transaction? diff --git a/openingd/dualopend_wiregen.c b/openingd/dualopend_wiregen.c index d5dd12ae9..fcc7931f6 100644 --- a/openingd/dualopend_wiregen.c +++ b/openingd/dualopend_wiregen.c @@ -365,19 +365,19 @@ bool fromwire_dualopend_got_offer_reply(const tal_t *ctx, const void *p, struct /* WIRE: DUALOPEND_GOT_RBF_OFFER */ /* dualopend->master: they offered a RBF */ -u8 *towire_dualopend_got_rbf_offer(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat opener_funding, u32 funding_feerate_per_kw, u32 locktime) +u8 *towire_dualopend_got_rbf_offer(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat their_funding, u32 funding_feerate_per_kw, u32 locktime) { u8 *p = tal_arr(ctx, u8, 0); towire_u16(&p, WIRE_DUALOPEND_GOT_RBF_OFFER); towire_channel_id(&p, channel_id); - towire_amount_sat(&p, opener_funding); + towire_amount_sat(&p, their_funding); towire_u32(&p, funding_feerate_per_kw); towire_u32(&p, locktime); return memcheck(p, tal_count(p)); } -bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_id, struct amount_sat *opener_funding, u32 *funding_feerate_per_kw, u32 *locktime) +bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_id, struct amount_sat *their_funding, u32 *funding_feerate_per_kw, u32 *locktime) { const u8 *cursor = p; size_t plen = tal_count(p); @@ -385,7 +385,7 @@ bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_ if (fromwire_u16(&cursor, &plen) != WIRE_DUALOPEND_GOT_RBF_OFFER) return false; fromwire_channel_id(&cursor, &plen, channel_id); - *opener_funding = fromwire_amount_sat(&cursor, &plen); + *their_funding = fromwire_amount_sat(&cursor, &plen); *funding_feerate_per_kw = fromwire_u32(&cursor, &plen); *locktime = fromwire_u32(&cursor, &plen); return cursor != NULL; @@ -393,24 +393,24 @@ bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_ /* WIRE: DUALOPEND_GOT_RBF_OFFER_REPLY */ /* master->dualopend: reply back with our funding info/contribs */ -u8 *towire_dualopend_got_rbf_offer_reply(const tal_t *ctx, struct amount_sat accepter_funding, const struct wally_psbt *psbt) +u8 *towire_dualopend_got_rbf_offer_reply(const tal_t *ctx, struct amount_sat our_funding, const struct wally_psbt *psbt) { u8 *p = tal_arr(ctx, u8, 0); towire_u16(&p, WIRE_DUALOPEND_GOT_RBF_OFFER_REPLY); - towire_amount_sat(&p, accepter_funding); + towire_amount_sat(&p, our_funding); towire_wally_psbt(&p, psbt); return memcheck(p, tal_count(p)); } -bool fromwire_dualopend_got_rbf_offer_reply(const tal_t *ctx, const void *p, struct amount_sat *accepter_funding, struct wally_psbt **psbt) +bool fromwire_dualopend_got_rbf_offer_reply(const tal_t *ctx, const void *p, struct amount_sat *our_funding, struct wally_psbt **psbt) { const u8 *cursor = p; size_t plen = tal_count(p); if (fromwire_u16(&cursor, &plen) != WIRE_DUALOPEND_GOT_RBF_OFFER_REPLY) return false; - *accepter_funding = fromwire_amount_sat(&cursor, &plen); + *our_funding = fromwire_amount_sat(&cursor, &plen); *psbt = fromwire_wally_psbt(ctx, &cursor, &plen); return cursor != NULL; } @@ -966,4 +966,4 @@ bool fromwire_dualopend_dev_memleak_reply(const void *p, bool *leak) *leak = fromwire_bool(&cursor, &plen); return cursor != NULL; } -// SHA256STAMP:e514a16cf96dfcaf44217cf344ce75e2b8cbcb460901c610d9b75e22937636cd +// SHA256STAMP:0f0daed93a4de2552ca122b969c4ac215ab89e3d5babc727b963fcf02f85980d diff --git a/openingd/dualopend_wiregen.h b/openingd/dualopend_wiregen.h index 8e7ee4811..f2f93b9cc 100644 --- a/openingd/dualopend_wiregen.h +++ b/openingd/dualopend_wiregen.h @@ -108,13 +108,13 @@ bool fromwire_dualopend_got_offer_reply(const tal_t *ctx, const void *p, struct /* WIRE: DUALOPEND_GOT_RBF_OFFER */ /* dualopend->master: they offered a RBF */ -u8 *towire_dualopend_got_rbf_offer(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat opener_funding, u32 funding_feerate_per_kw, u32 locktime); -bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_id, struct amount_sat *opener_funding, u32 *funding_feerate_per_kw, u32 *locktime); +u8 *towire_dualopend_got_rbf_offer(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat their_funding, u32 funding_feerate_per_kw, u32 locktime); +bool fromwire_dualopend_got_rbf_offer(const void *p, struct channel_id *channel_id, struct amount_sat *their_funding, u32 *funding_feerate_per_kw, u32 *locktime); /* WIRE: DUALOPEND_GOT_RBF_OFFER_REPLY */ /* master->dualopend: reply back with our funding info/contribs */ -u8 *towire_dualopend_got_rbf_offer_reply(const tal_t *ctx, struct amount_sat accepter_funding, const struct wally_psbt *psbt); -bool fromwire_dualopend_got_rbf_offer_reply(const tal_t *ctx, const void *p, struct amount_sat *accepter_funding, struct wally_psbt **psbt); +u8 *towire_dualopend_got_rbf_offer_reply(const tal_t *ctx, struct amount_sat our_funding, const struct wally_psbt *psbt); +bool fromwire_dualopend_got_rbf_offer_reply(const tal_t *ctx, const void *p, struct amount_sat *our_funding, struct wally_psbt **psbt); /* WIRE: DUALOPEND_RBF_VALIDATE */ /* dualopend->master: is this a valid RBF candidate transaction? */ @@ -223,4 +223,4 @@ bool fromwire_dualopend_dev_memleak_reply(const void *p, bool *leak); #endif /* LIGHTNING_OPENINGD_DUALOPEND_WIREGEN_H */ -// SHA256STAMP:e514a16cf96dfcaf44217cf344ce75e2b8cbcb460901c610d9b75e22937636cd +// SHA256STAMP:0f0daed93a4de2552ca122b969c4ac215ab89e3d5babc727b963fcf02f85980d