plugins: support failure_message in invoice and htlc_accepted hooks.

As promised in the Changelog when we converted from failcodes to messages
internally.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2020-02-21 15:40:45 +10:30
parent c9e73dc4e0
commit faac4b28ad
5 changed files with 91 additions and 83 deletions

View File

@ -636,8 +636,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.
@ -763,12 +763,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
{
@ -899,7 +899,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

View File

@ -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)

View File

@ -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")) {
const jsmntok_t *failmsgtok, *failcodetok;
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;
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:

View File

@ -9,9 +9,8 @@ plugin = Plugin()
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()

View File

@ -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'}