Skip to content

Commit

Permalink
plugins: support failure_message in invoice and htlc_accepted hooks.
Browse files Browse the repository at this point in the history
As promised in the Changelog when we converted from failcodes to messages
internally.

Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell committed Feb 3, 2020
1 parent 6b03ae2 commit a6551a0
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 72 deletions.
12 changes: 6 additions & 6 deletions doc/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,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], or otherwise an empty object
It can return a `failure_message` field as defined for final
nodes in [BOLT 4][bolt4-failure-messages], or otherwise an empty object
to accept the payment.


Expand Down Expand Up @@ -667,12 +667,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
{
Expand Down Expand Up @@ -796,7 +796,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
12 changes: 12 additions & 0 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ static const u8 *hook_gives_failmsg(const tal_t *ctx,
if (!buffer)
return NULL;

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");
if (!t)
return NULL;
Expand Down
125 changes: 65 additions & 60 deletions lightningd/peer_htlcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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");
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 2 additions & 3 deletions tests/plugins/fail_htlcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
5 changes: 2 additions & 3 deletions tests/plugins/reject_some_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down

0 comments on commit a6551a0

Please sign in to comment.