From 7499f7ddd479d73eb0f7f0a21d4de90e9d0601bd Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 3 Jan 2019 17:27:46 +0100 Subject: [PATCH] plugin: Add the htlc_accepted hook This is a rather simple hook that allows a plugin to take control over HTLCs that were accepted, but weren't resolved as part of an invoice or forwarded to the next hop yet. The goal is to allow plugins to terminate a route early, perform intermediate checks before the payment is accepted (check inventory or service delivery before accepting in order to avoid a refund for example) or handle an onion differently if it has a different realm (cross-chain atomic swaps). This doesn't implement serializing the payload or deserializing it, instead just passes the full context along. The details for serializing and deserializing will be implemented in a future commit. Signed-off-by: Christian Decker --- lightningd/peer_htlcs.c | 107 ++++++++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index bfdb68c97..92e80169a 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -612,16 +613,90 @@ static void channel_resolve_reply(struct subd *gossip, const u8 *msg, tal_free(gr); } +/** + * Data passed to the plugin, and as the context for the hook callback + */ +struct htlc_accepted_hook_payload { + struct route_step *route_step; + struct htlc_in *hin; + struct channel *channel; + struct lightningd *ld; +}; + +/** + * Response type from the plugin + */ +struct htlc_accepted_hook_response { +}; + +/** + * Parses the JSON-RPC response into a struct understood by the callback. + */ +static struct htlc_accepted_hook_response * +htlc_accepted_hook_deserialize(const tal_t *ctx, const char *buffer, + const jsmntok_t *toks) +{ + return NULL; +} + +static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, + struct json_stream *s) +{ +} + +/** + * Callback when a plugin answers to the htlc_accepted hook + */ +static void +htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, + const char *buffer, const jsmntok_t *toks) +{ + struct route_step *rs = request->route_step; + struct htlc_in *hin = request->hin; + struct channel *channel = request->channel; + struct lightningd *ld = request->ld; + u8 *req; + + /* TODO(cdecker) Assign to *response once we actually use it */ + htlc_accepted_hook_deserialize(request, buffer, toks); + + if (rs->nextcase == ONION_FORWARD) { + struct gossip_resolve *gr = tal(ld, struct gossip_resolve); + + gr->next_onion = serialize_onionpacket(gr, rs->next); + gr->next_channel = rs->hop_data.channel_id; + gr->amt_to_forward = rs->hop_data.amt_forward; + gr->outgoing_cltv_value = rs->hop_data.outgoing_cltv; + gr->hin = hin; + + req = towire_gossip_get_channel_peer(tmpctx, &gr->next_channel); + log_debug(channel->log, "Asking gossip to resolve channel %s", + type_to_string(tmpctx, struct short_channel_id, + &gr->next_channel)); + subd_req(hin, ld->gossip, req, -1, 0, + channel_resolve_reply, gr); + } else + handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash, + rs->hop_data.amt_forward, + rs->hop_data.outgoing_cltv); + tal_free(request); +} + +REGISTER_PLUGIN_HOOK(htlc_accepted, htlc_accepted_hook_callback, + struct htlc_accepted_hook_payload *, + htlc_accepted_hook_serialize, + struct htlc_accepted_hook_payload *); + /* Everyone is committed to this htlc of theirs */ static bool peer_accepted_htlc(struct channel *channel, u64 id, enum onion_type *failcode) { struct htlc_in *hin; - u8 *req; struct route_step *rs; struct onionpacket *op; struct lightningd *ld = channel->peer->ld; + struct htlc_accepted_hook_payload *hook_payload; hin = find_htlc_in(&ld->htlcs_in, channel, id); if (!hin) { @@ -695,31 +770,23 @@ static bool peer_accepted_htlc(struct channel *channel, } /* Unknown realm isn't a bad onion, it's a normal failure. */ + /* FIXME: if we want hooks to handle foreign realms we should + * move this check to the hook callback. */ if (rs->hop_data.realm != 0) { *failcode = WIRE_INVALID_REALM; goto out; } - if (rs->nextcase == ONION_FORWARD) { - struct gossip_resolve *gr = tal(ld, struct gossip_resolve); - - gr->next_onion = serialize_onionpacket(gr, rs->next); - gr->next_channel = rs->hop_data.channel_id; - gr->amt_to_forward = rs->hop_data.amt_forward; - gr->outgoing_cltv_value = rs->hop_data.outgoing_cltv; - gr->hin = hin; - - req = towire_gossip_get_channel_peer(tmpctx, &gr->next_channel); - log_debug(channel->log, "Asking gossip to resolve channel %s", - type_to_string(tmpctx, struct short_channel_id, - &gr->next_channel)); - subd_req(hin, ld->gossip, req, -1, 0, - channel_resolve_reply, gr); - } else - handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash, - rs->hop_data.amt_forward, - rs->hop_data.outgoing_cltv); + /* It's time to package up all the information and call the + * hook so plugins can interject if they want */ + hook_payload = tal(hin, struct htlc_accepted_hook_payload); + hook_payload->route_step = tal_steal(hook_payload, rs); + hook_payload->ld = ld; + hook_payload->hin = hin; + hook_payload->channel = channel; + plugin_hook_call_htlc_accepted(ld, hook_payload, hook_payload); + /* Falling through here is ok, after all the HTLC locked */ *failcode = 0; out: log_debug(channel->log, "their htlc %"PRIu64" %s",