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 <decker.christian@gmail.com>
This commit is contained in:
parent
9a4cf7dda7
commit
7499f7ddd4
|
@ -22,6 +22,7 @@
|
|||
#include <lightningd/pay.h>
|
||||
#include <lightningd/peer_control.h>
|
||||
#include <lightningd/peer_htlcs.h>
|
||||
#include <lightningd/plugin_hook.h>
|
||||
#include <lightningd/subd.h>
|
||||
#include <onchaind/gen_onchain_wire.h>
|
||||
#include <onchaind/onchain_wire.h>
|
||||
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue