diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index af0a37769271..c617f49c0e7f 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -596,8 +596,8 @@ This hook is called whenever a valid payment for an unpaid invoice has arrived. The hook is sparse on purpose, since the plugin can use the JSON-RPC `listinvoices` command to get additional details about this invoice. -It can return a non-zero `failure_code` field as defined for final -nodes in [BOLT 4][bolt4-failure-codes], a `result` field with the string +It can return a `failure_message` field as defined for final +nodes in [BOLT 4][bolt4-failure-messages], a `result` field with the string `reject` to fail it with `incorrect_or_unknown_payment_details`, or a `result` field with the string `continue` to accept the payment. @@ -723,12 +723,12 @@ usual checks such as sufficient fees and CLTV deltas are still enforced. ```json { "result": "fail", - "failure_code": 4301 + "failure_message": "2002" } ``` -`fail` will tell `lightningd` to fail the HTLC with a given numeric -`failure_code` (please refer to the [spec][bolt4-failure-codes] for details). +`fail` will tell `lightningd` to fail the HTLC with a given hex-encoded +`failure_message` (please refer to the [spec][bolt4-failure-messages] for details: `incorrect_or_unknown_payment_details` is the most common). ```json { @@ -853,7 +853,7 @@ compatibility should the semantics be changed in future. [jsonrpc-spec]: https://www.jsonrpc.org/specification [jsonrpc-notification-spec]: https://www.jsonrpc.org/specification#notification [bolt4]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md -[bolt4-failure-codes]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages +[bolt4-failure-messages]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages [bolt2-open-channel]: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#the-open_channel-message [sendcustommsg]: lightning-dev-sendcustommsg.7.html [oddok]: https://github.com/lightningnetwork/lightning-rfc/blob/master/00-introduction.md#its-ok-to-be-odd diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 0c1a01640433..e63a9d30db4d 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -187,33 +187,38 @@ static const u8 *hook_gives_failmsg(const tal_t *ctx, toks[0].end - toks[0].start, buffer); } + t = json_get_member(buffer, toks, "failure_message"); + if (t) { + const u8 *failmsg = json_tok_bin_from_hex(ctx, buffer, t); + if (!failmsg) + fatal("Invalid invoice_payment_hook failure_message: %.*s", + toks[0].end - toks[1].start, buffer); + return failmsg; + } + + if (!deprecated_apis) + return NULL; + t = json_get_member(buffer, toks, "failure_code"); -#ifdef COMPAT_V080 - if (!t && deprecated_apis) { + if (!t) { static bool warned = false; if (!warned) { warned = true; log_unusual(log, "Plugin did not return object with " - "'result' or 'failure_code' fields. " + "'result' or 'failure_message' fields. " "This is now deprecated and you should " "return {'result': 'continue' } or " "{'result': 'reject'} or " - "{'failure_code': 42} instead."); + "{'failure_message'... instead."); } - return false; + return failmsg_incorrect_or_unknown(ctx, hin); } -#endif /* defined(COMPAT_V080) */ - if (!t) - fatal("Invalid invoice_payment_hook response, expecting " - "'result' or 'failure_code' field: %.*s", - toks[0].end - toks[0].start, buffer); if (!json_to_number(buffer, t, &val)) fatal("Invalid invoice_payment_hook failure_code: %.*s", toks[0].end - toks[1].start, buffer); - /* FIXME: Allow hook to return its own failmsg directly? */ if (val == WIRE_TEMPORARY_NODE_FAILURE) return towire_temporary_node_failure(ctx); if (val != WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index 6565ad9ba053..a7204008e6e4 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -747,17 +747,50 @@ enum htlc_accepted_result { htlc_accepted_resolve, }; +/* We only handle the simplest cases here */ +static u8 *convert_failcode(const tal_t *ctx, + struct lightningd *ld, + unsigned int failure_code) +{ + switch (failure_code) { + case WIRE_INVALID_REALM: + return towire_invalid_realm(ctx); + case WIRE_TEMPORARY_NODE_FAILURE: + return towire_temporary_node_failure(ctx); + case WIRE_PERMANENT_NODE_FAILURE: + return towire_permanent_node_failure(ctx); + case WIRE_REQUIRED_NODE_FEATURE_MISSING: + return towire_required_node_feature_missing(ctx); + case WIRE_CHANNEL_DISABLED: + return towire_channel_disabled(ctx); + case WIRE_PERMANENT_CHANNEL_FAILURE: + return towire_permanent_channel_failure(ctx); + case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: + return towire_required_channel_feature_missing(ctx); + case WIRE_UNKNOWN_NEXT_PEER: + return towire_unknown_next_peer(ctx); + default: + log_broken(ld->log, + "htlc_accepted_hook plugin returned failure_code %u," + " turning to WIRE_TEMPORARY_NODE_FAILURE", + failure_code); + return towire_temporary_node_failure(ctx); + } +} + /** * Parses the JSON-RPC response into a struct understood by the callback. */ -static enum htlc_accepted_result htlc_accepted_hook_deserialize(const char *buffer, const jsmntok_t *toks, - /* If accepted */ - struct preimage *payment_preimage, - /* If rejected */ - enum onion_type *failure_code, - u8 **channel_update) +static enum htlc_accepted_result +htlc_accepted_hook_deserialize(const tal_t *ctx, + struct lightningd *ld, + const char *buffer, const jsmntok_t *toks, + /* If accepted */ + struct preimage *payment_preimage, + /* If rejected (tallocated off ctx) */ + const u8 **failmsg) { - const jsmntok_t *resulttok, *failcodetok, *paykeytok, *chanupdtok; + const jsmntok_t *resulttok, *paykeytok; enum htlc_accepted_result result; if (!toks || !buffer) @@ -777,23 +810,31 @@ static enum htlc_accepted_result htlc_accepted_hook_deserialize(const char *buff } if (json_tok_streq(buffer, resulttok, "fail")) { - 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, failure_code)) - fatal("Plugin provided a non-numeric failcode " - "in response to an htlc_accepted hook"); - - if (!failcodetok) - *failure_code = WIRE_TEMPORARY_NODE_FAILURE; - - if (chanupdtok) - *channel_update = - json_tok_bin_from_hex(buffer, buffer, chanupdtok); - else - *channel_update = NULL; + const jsmntok_t *failmsgtok, *failcodetok; + result = htlc_accepted_fail; + failmsgtok = json_get_member(buffer, toks, "failure_message"); + if (failmsgtok) { + *failmsg = json_tok_bin_from_hex(ctx, buffer, + failmsgtok); + if (!*failmsg) + fatal("Bad failure_message for htlc_accepted" + " hook: %.*s", + failmsgtok->end - failmsgtok->start, + buffer + failmsgtok->start); + } else if (deprecated_apis + && (failcodetok = json_get_member(buffer, toks, + "failure_code"))) { + unsigned int failcode; + if (!json_to_number(buffer, failcodetok, &failcode)) + fatal("Bad failure_code for htlc_accepted" + " hook: %.*s", + failcodetok->end + - failcodetok->start, + buffer + failcodetok->start); + *failmsg = convert_failcode(ctx, ld, failcode); + } else + *failmsg = towire_temporary_node_failure(ctx); } else if (json_tok_streq(buffer, resulttok, "resolve")) { result = htlc_accepted_resolve; paykeytok = json_get_member(buffer, toks, "payment_key"); @@ -888,10 +929,8 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, struct preimage payment_preimage; u8 *req; enum htlc_accepted_result result; - enum onion_type failure_code; const u8 *failmsg; - u8 *channel_update; - result = htlc_accepted_hook_deserialize(buffer, toks, &payment_preimage, &failure_code, &channel_update); + result = htlc_accepted_hook_deserialize(request, ld, buffer, toks, &payment_preimage, &failmsg); switch (result) { case htlc_accepted_continue: @@ -925,40 +964,6 @@ htlc_accepted_hook_callback(struct htlc_accepted_hook_payload *request, case htlc_accepted_fail: log_debug(channel->log, "Failing incoming HTLC as instructed by plugin hook"); - /* FIXME: Deprecate failure_code in favor of failure_message! */ - /* We only handle the simplest cases here */ - switch (failure_code) { - case WIRE_INVALID_REALM: - failmsg = towire_invalid_realm(NULL); - break; - case WIRE_TEMPORARY_NODE_FAILURE: - failmsg = towire_temporary_node_failure(NULL); - break; - case WIRE_PERMANENT_NODE_FAILURE: - failmsg = towire_permanent_node_failure(NULL); - break; - case WIRE_REQUIRED_NODE_FEATURE_MISSING: - failmsg = towire_required_node_feature_missing(NULL); - break; - case WIRE_CHANNEL_DISABLED: - failmsg = towire_channel_disabled(NULL); - break; - case WIRE_PERMANENT_CHANNEL_FAILURE: - failmsg = towire_permanent_channel_failure(NULL); - break; - case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: - failmsg = towire_required_channel_feature_missing(NULL); - break; - case WIRE_UNKNOWN_NEXT_PEER: - failmsg = towire_unknown_next_peer(NULL); - break; - default: - log_broken(channel->log, - "htlc_accepted_hook plugin returned %u," - " turning to WIRE_TEMPORARY_NODE_FAILURE", - failure_code); - failmsg = towire_temporary_node_failure(NULL); - } local_fail_in_htlc(hin, take(failmsg)); break; case htlc_accepted_resolve: diff --git a/tests/plugins/fail_htlcs.py b/tests/plugins/fail_htlcs.py index e36b67b0b3bd..9141549a0728 100755 --- a/tests/plugins/fail_htlcs.py +++ b/tests/plugins/fail_htlcs.py @@ -9,9 +9,8 @@ def on_htlc_accepted(onion, plugin, **kwargs): plugin.log("Failing htlc on purpose") plugin.log("onion: %r" % (onion)) - # FIXME: Define this! - WIRE_TEMPORARY_NODE_FAILURE = 0x2002 - return {"result": "fail", "failure_code": WIRE_TEMPORARY_NODE_FAILURE} + # WIRE_TEMPORARY_NODE_FAILURE = 0x2002 + return {"result": "fail", "failure_message": "2002"} plugin.run() diff --git a/tests/plugins/reject_some_invoices.py b/tests/plugins/reject_some_invoices.py index c326b58298ed..1c5b3d2cd18a 100755 --- a/tests/plugins/reject_some_invoices.py +++ b/tests/plugins/reject_some_invoices.py @@ -16,9 +16,8 @@ def on_payment(payment, plugin, **kwargs): print("preimage={}".format(payment['preimage'])) if payment['preimage'].endswith('0'): - # FIXME: Define this! - WIRE_TEMPORARY_NODE_FAILURE = 0x2002 - return {'failure_code': WIRE_TEMPORARY_NODE_FAILURE} + # WIRE_TEMPORARY_NODE_FAILURE = 0x2002 + return {'failure_message': "2002"} return {'result': 'continue'}