From 2b81e02a2ea3f9d702f0e613111c65b337a52bbc Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 9 Jan 2019 16:12:35 +0100 Subject: [PATCH] plugin: Parse response for htlc_accepted hook Signed-off-by: Christian Decker --- contrib/plugins/helloworld.py | 2 +- lightningd/peer_htlcs.c | 138 ++++++++++++++++++++++++++++------ wallet/test/run-wallet.c | 17 +++++ 3 files changed, 134 insertions(+), 23 deletions(-) diff --git a/contrib/plugins/helloworld.py b/contrib/plugins/helloworld.py index 3cc2462a2..335b01207 100755 --- a/contrib/plugins/helloworld.py +++ b/contrib/plugins/helloworld.py @@ -38,7 +38,7 @@ def on_disconnect(plugin, id): def on_htlc_accepted(onion, htlc, plugin): plugin.log('on_htlc_accepted called') time.sleep(20) - return None + return {'result': 'continue'} plugin.add_option('greeting', 'Hello', 'The greeting I should use.') diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index ac497b122..cb3ce44d9 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -624,10 +625,22 @@ struct htlc_accepted_hook_payload { u8 *next_onion; }; +/* The possible return value types that a plugin may return for the + * `htlc_accepted` hook. */ +enum htlc_accepted_result { + htlc_accepted_continue, + htlc_accepted_fail, + htlc_accepted_resolve, +}; + /** * Response type from the plugin */ struct htlc_accepted_hook_response { + enum htlc_accepted_result result; + struct preimage payment_key; + enum onion_type failure_code; + u8 *channel_update; }; /** @@ -637,7 +650,64 @@ static struct htlc_accepted_hook_response * htlc_accepted_hook_deserialize(const tal_t *ctx, const char *buffer, const jsmntok_t *toks) { - return NULL; + struct htlc_accepted_hook_response *response; + const jsmntok_t *resulttok, *failcodetok, *paykeytok, *chanupdtok; + + if (!toks || !buffer) + return NULL; + + resulttok = json_get_member(buffer, toks, "result"); + + /* If the result is "continue" we can just return NULL since + * this is the default behavior for this hook anyway */ + if (!resulttok) { + fatal("Plugin return value does not contain 'result' key %s", + json_strdup(tmpctx, buffer, toks)); + } + + if (json_tok_streq(buffer, resulttok, "continue")) { + return NULL; + } + + response = tal(ctx, struct htlc_accepted_hook_response); + if (json_tok_streq(buffer, resulttok, "fail")) { + response->result = htlc_accepted_fail; + failcodetok = json_get_member(buffer, toks, "failure_code"); + chanupdtok = json_get_member(buffer, toks, "channel_update"); + if (failcodetok && !json_to_number(buffer, failcodetok, + &response->failure_code)) + fatal("Plugin provided a non-numeric failcode " + "in response to an htlc_accepted hook"); + + if (!failcodetok) + response->failure_code = WIRE_TEMPORARY_NODE_FAILURE; + + if (chanupdtok) + response->channel_update = + json_tok_bin_from_hex(response, buffer, chanupdtok); + else + response->channel_update = NULL; + + } else if (json_tok_streq(buffer, resulttok, "resolve")) { + response->result = htlc_accepted_resolve; + paykeytok = json_get_member(buffer, toks, "payment_key"); + if (!paykeytok) + fatal( + "Plugin did not specify a 'payment_key' in return " + "value to the htlc_accepted hook: %s", + json_strdup(tmpctx, buffer, resulttok)); + + if (!json_to_preimage(buffer, paykeytok, + &response->payment_key)) + fatal("Plugin specified an invalid 'payment_key': %s", + json_tok_full(buffer, resulttok)); + } else { + fatal("Plugin responded with an unknown result to the " + "htlc_accepted hook: %s", + json_strdup(tmpctx, buffer, resulttok)); + } + + return response; } static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, @@ -679,29 +749,53 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, struct channel *channel = request->channel; struct lightningd *ld = request->ld; u8 *req; + enum htlc_accepted_result result; + struct htlc_accepted_hook_response *response; + response = htlc_accepted_hook_deserialize(request, buffer, toks); - /* 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 = tal_steal(gr, request->next_onion); - 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; + if (response) + result = response->result; + else + result = htlc_accepted_continue; + + switch (result) { + case htlc_accepted_continue: + if (rs->nextcase == ONION_FORWARD) { + struct gossip_resolve *gr = + tal(ld, struct gossip_resolve); + + gr->next_onion = tal_steal(gr, request->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); + + break; + case htlc_accepted_fail: + log_debug(channel->log, + "Failing incoming HTLC as instructed by plugin hook"); + fail_in_htlc(hin, response->failure_code, NULL, NULL); + break; + case htlc_accepted_resolve: + fulfill_htlc(hin, &response->payment_key); + break; + } - 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); } diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 29dc4dd4e..2706ff4a1 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -299,9 +299,15 @@ void json_object_end(struct json_stream *js UNNEEDED) /* Generated stub for json_object_start */ void json_object_start(struct json_stream *ks UNNEEDED, const char *fieldname UNNEEDED) { fprintf(stderr, "json_object_start called!\n"); abort(); } +/* Generated stub for json_strdup */ +char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_strdup called!\n"); abort(); } /* Generated stub for json_stream_success */ struct json_stream *json_stream_success(struct command *cmd UNNEEDED) { fprintf(stderr, "json_stream_success called!\n"); abort(); } +/* Generated stub for json_tok_bin_from_hex */ +u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } /* Generated stub for json_tok_channel_id */ bool json_tok_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct channel_id *cid UNNEEDED) @@ -319,6 +325,17 @@ bool json_tok_streq(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "json_to_node_id called!\n"); abort(); } +/* Generated stub for json_to_number */ +bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + unsigned int *num UNNEEDED) +{ fprintf(stderr, "json_to_number called!\n"); abort(); } +/* Generated stub for json_to_preimage */ +bool json_to_preimage(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct preimage *preimage UNNEEDED) +{ fprintf(stderr, "json_to_preimage called!\n"); abort(); } +/* Generated stub for json_to_pubkey */ +bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct pubkey *pubkey UNNEEDED) +{ fprintf(stderr, "json_to_pubkey called!\n"); abort(); } /* Generated stub for json_to_short_channel_id */ bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct short_channel_id *scid UNNEEDED,