lightningd: make local htlc failures pass a wiremsg for errors, not a failcode.

Unfortunately the invoice_payment_hook can give us a failcode, so I simply
restrict it to the two sensible ones.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-deprecated: plugins: invoice_payment_hook "failure_code" only handles simple cases now, use "failure_message".
This commit is contained in:
Rusty Russell 2020-02-21 15:38:39 +10:30
parent 5af3a135be
commit 590b2db88e
6 changed files with 78 additions and 42 deletions

View File

@ -14,7 +14,7 @@ static void htlc_set_hin_destroyed(struct htlc_in *hin,
/* Don't try to re-fail this HTLC! */ /* Don't try to re-fail this HTLC! */
tal_arr_remove(&set->htlcs, i); tal_arr_remove(&set->htlcs, i);
/* Kind of the correct failure code. */ /* Kind of the correct failure code. */
htlc_set_fail(set, WIRE_MPP_TIMEOUT); htlc_set_fail(set, take(towire_mpp_timeout(NULL)));
return; return;
} }
} }
@ -35,15 +35,19 @@ static void destroy_htlc_set(struct htlc_set *set,
*/ */
static void timeout_htlc_set(struct htlc_set *set) static void timeout_htlc_set(struct htlc_set *set)
{ {
htlc_set_fail(set, WIRE_MPP_TIMEOUT); htlc_set_fail(set, take(towire_mpp_timeout(NULL)));
} }
void htlc_set_fail(struct htlc_set *set, enum onion_type failcode) void htlc_set_fail(struct htlc_set *set, const u8 *failmsg TAKES)
{ {
/* Don't let local_fail_in_htlc take! */
if (taken(failmsg))
tal_steal(set, failmsg);
for (size_t i = 0; i < tal_count(set->htlcs); i++) { for (size_t i = 0; i < tal_count(set->htlcs); i++) {
/* Don't remove from set */ /* Don't remove from set */
tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set); tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set);
fail_htlc(set->htlcs[i], failcode); local_fail_in_htlc(set->htlcs[i], failmsg);
} }
tal_free(set); tal_free(set);
} }
@ -101,7 +105,8 @@ void htlc_set_add(struct lightningd *ld,
details = invoice_check_payment(tmpctx, ld, &hin->payment_hash, details = invoice_check_payment(tmpctx, ld, &hin->payment_hash,
total_msat, payment_secret); total_msat, payment_secret);
if (!details) { if (!details) {
fail_htlc(hin, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS); local_fail_in_htlc(hin,
take(failmsg_incorrect_or_unknown(NULL, hin)));
return; return;
} }
@ -125,7 +130,7 @@ void htlc_set_add(struct lightningd *ld,
/* We check this now, since we want to fail with this as soon /* We check this now, since we want to fail with this as soon
* as possible, to avoid other probing attacks. */ * as possible, to avoid other probing attacks. */
if (!payment_secret) { if (!payment_secret) {
fail_htlc(hin, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS); local_fail_in_htlc(hin, take(failmsg_incorrect_or_unknown(NULL, hin)));
return; return;
} }
tal_arr_expand(&set->htlcs, hin); tal_arr_expand(&set->htlcs, hin);
@ -147,7 +152,9 @@ void htlc_set_add(struct lightningd *ld,
&set->total_msat), &set->total_msat),
type_to_string(tmpctx, struct amount_msat, type_to_string(tmpctx, struct amount_msat,
&total_msat)); &total_msat));
htlc_set_fail(set, WIRE_FINAL_INCORRECT_HTLC_AMOUNT); htlc_set_fail(set,
take(towire_final_incorrect_htlc_amount(NULL,
hin->msat)));
return; return;
} }
@ -164,7 +171,9 @@ void htlc_set_add(struct lightningd *ld,
&set->so_far), &set->so_far),
type_to_string(tmpctx, struct amount_msat, type_to_string(tmpctx, struct amount_msat,
&hin->msat)); &hin->msat));
htlc_set_fail(set, WIRE_FINAL_INCORRECT_HTLC_AMOUNT); htlc_set_fail(set,
take(towire_final_incorrect_htlc_amount(NULL,
hin->msat)));
return; return;
} }
@ -183,7 +192,8 @@ void htlc_set_add(struct lightningd *ld,
* - MUST require `payment_secret` for all HTLCs in the set. */ * - MUST require `payment_secret` for all HTLCs in the set. */
/* This catches the case of the first payment in a set. */ /* This catches the case of the first payment in a set. */
if (!payment_secret) { if (!payment_secret) {
htlc_set_fail(set, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS); htlc_set_fail(set,
take(failmsg_incorrect_or_unknown(NULL, hin)));
return; return;
} }
} }

View File

@ -49,7 +49,7 @@ void htlc_set_add(struct lightningd *ld,
const struct secret *payment_secret); const struct secret *payment_secret);
/* Fail every htlc in the set: frees set */ /* Fail every htlc in the set: frees set */
void htlc_set_fail(struct htlc_set *set, enum onion_type failcode); void htlc_set_fail(struct htlc_set *set, const u8 *failmsg TAKES);
/* Fulfill every htlc in the set: frees set */ /* Fulfill every htlc in the set: frees set */
void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage); void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage);

View File

@ -162,10 +162,11 @@ static void invoice_payload_remove_set(struct htlc_set *set,
payload->set = NULL; payload->set = NULL;
} }
static bool hook_gives_failcode(struct log *log, static const u8 *hook_gives_failmsg(const tal_t *ctx,
struct log *log,
const struct htlc_in *hin,
const char *buffer, const char *buffer,
const jsmntok_t *toks, const jsmntok_t *toks)
enum onion_type *failcode)
{ {
const jsmntok_t *resulttok; const jsmntok_t *resulttok;
const jsmntok_t *t; const jsmntok_t *t;
@ -173,15 +174,14 @@ static bool hook_gives_failcode(struct log *log,
/* No plugin registered on hook at all? */ /* No plugin registered on hook at all? */
if (!buffer) if (!buffer)
return false; return NULL;
resulttok = json_get_member(buffer, toks, "result"); resulttok = json_get_member(buffer, toks, "result");
if (resulttok) { if (resulttok) {
if (json_tok_streq(buffer, resulttok, "continue")) { if (json_tok_streq(buffer, resulttok, "continue")) {
return false; return NULL;
} else if (json_tok_streq(buffer, resulttok, "reject")) { } else if (json_tok_streq(buffer, resulttok, "reject")) {
*failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS; return failmsg_incorrect_or_unknown(ctx, hin);
return true;
} else } else
fatal("Invalid invoice_payment hook result: %.*s", fatal("Invalid invoice_payment hook result: %.*s",
toks[0].end - toks[0].start, buffer); toks[0].end - toks[0].start, buffer);
@ -213,13 +213,16 @@ static bool hook_gives_failcode(struct log *log,
fatal("Invalid invoice_payment_hook failure_code: %.*s", fatal("Invalid invoice_payment_hook failure_code: %.*s",
toks[0].end - toks[1].start, buffer); toks[0].end - toks[1].start, buffer);
/* UPDATE isn't valid for final nodes to return, and I think /* FIXME: Allow hook to return its own failmsg directly? */
* we assert elsewhere that we don't do this! */ if (val == WIRE_TEMPORARY_NODE_FAILURE)
if (val & UPDATE) return towire_temporary_node_failure(ctx);
fatal("Invalid invoice_payment_hook UPDATE failure_code: %.*s", if (val != WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
toks[0].end - toks[1].start, buffer); log_broken(hin->key.channel->log,
*failcode = val; "invoice_payment hook returned failcode %u,"
return true; " changing to incorrect_or_unknown_payment_details",
val);
return failmsg_incorrect_or_unknown(ctx, hin);
} }
static void static void
@ -229,7 +232,7 @@ invoice_payment_hook_cb(struct invoice_payment_hook_payload *payload,
{ {
struct lightningd *ld = payload->ld; struct lightningd *ld = payload->ld;
struct invoice invoice; struct invoice invoice;
enum onion_type failcode; const u8 *failmsg;
/* We notify here to benefit from the payload and because the hook callback is /* We notify here to benefit from the payload and because the hook callback is
* called even if the hook is not registered. */ * called even if the hook is not registered. */
@ -249,14 +252,16 @@ invoice_payment_hook_cb(struct invoice_payment_hook_payload *payload,
/* If invoice gets paid meanwhile (plugin responds out-of-order?) then /* If invoice gets paid meanwhile (plugin responds out-of-order?) then
* we can also fail */ * we can also fail */
if (!wallet_invoice_find_by_label(ld->wallet, &invoice, payload->label)) { if (!wallet_invoice_find_by_label(ld->wallet, &invoice, payload->label)) {
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS; htlc_set_fail(payload->set, take(failmsg_incorrect_or_unknown(
htlc_set_fail(payload->set, failcode); NULL, payload->set->htlcs[0])));
return; return;
} }
/* Did we have a hook result? */ /* Did we have a hook result? */
if (hook_gives_failcode(ld->log, buffer, toks, &failcode)) { failmsg = hook_gives_failmsg(NULL, ld->log,
htlc_set_fail(payload->set, failcode); payload->set->htlcs[0], buffer, toks);
if (failmsg) {
htlc_set_fail(payload->set, take(failmsg));
return; return;
} }

View File

@ -295,12 +295,21 @@ static void local_fail_htlc(struct htlc_in *hin, enum onion_type failcode,
fail_in_htlc(hin, failcode, NULL, out_channel_update); fail_in_htlc(hin, failcode, NULL, out_channel_update);
} }
void fail_htlc(struct htlc_in *hin, enum onion_type failcode) void local_fail_in_htlc(struct htlc_in *hin, const u8 *failmsg TAKES)
{ {
assert(failcode); /* FIXME: pass failmsg through! */
/* Final hop never sends an UPDATE. */ local_fail_htlc(hin, fromwire_peektype(failmsg), NULL);
assert(!(failcode & UPDATE)); if (taken(failmsg))
local_fail_htlc(hin, failcode, NULL); tal_free(failmsg);
}
/* Helper to create (common) WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS */
const u8 *failmsg_incorrect_or_unknown(const tal_t *ctx,
const struct htlc_in *hin)
{
return towire_incorrect_or_unknown_payment_details(
ctx, hin->msat,
get_block_height(hin->key.channel->owner->ld->topology));
} }
/* localfail are for handing to the local payer if it's local. */ /* localfail are for handing to the local payer if it's local. */
@ -443,7 +452,7 @@ static void handle_localpay(struct htlc_in *hin,
struct amount_msat total_msat, struct amount_msat total_msat,
const struct secret *payment_secret) const struct secret *payment_secret)
{ {
enum onion_type failcode; const u8 *failmsg;
struct lightningd *ld = hin->key.channel->peer->ld; struct lightningd *ld = hin->key.channel->peer->ld;
/* BOLT #4: /* BOLT #4:
@ -467,7 +476,7 @@ static void handle_localpay(struct htlc_in *hin,
* *
* The amount in the HTLC doesn't match the value in the onion. * The amount in the HTLC doesn't match the value in the onion.
*/ */
failcode = WIRE_FINAL_INCORRECT_HTLC_AMOUNT; failmsg = towire_final_incorrect_htlc_amount(NULL, hin->msat);
goto fail; goto fail;
} }
@ -480,7 +489,8 @@ static void handle_localpay(struct htlc_in *hin,
* The CLTV expiry in the HTLC doesn't match the value in the onion. * The CLTV expiry in the HTLC doesn't match the value in the onion.
*/ */
if (!check_cltv(hin, hin->cltv_expiry, outgoing_cltv_value, 0)) { if (!check_cltv(hin, hin->cltv_expiry, outgoing_cltv_value, 0)) {
failcode = WIRE_FINAL_INCORRECT_CLTV_EXPIRY; failmsg = towire_final_incorrect_cltv_expiry(NULL,
hin->cltv_expiry);
goto fail; goto fail;
} }
@ -497,7 +507,7 @@ static void handle_localpay(struct htlc_in *hin,
hin->cltv_expiry, hin->cltv_expiry,
get_block_height(ld->topology), get_block_height(ld->topology),
ld->config.cltv_final); ld->config.cltv_final);
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS; failmsg = failmsg_incorrect_or_unknown(NULL, hin);
goto fail; goto fail;
} }
@ -505,7 +515,7 @@ static void handle_localpay(struct htlc_in *hin,
return; return;
fail: fail:
fail_htlc(hin, failcode); local_fail_in_htlc(hin, take(failmsg));
} }
/* /*

View File

@ -71,11 +71,15 @@ void htlcs_resubmit(struct lightningd *ld,
/* For HTLCs which terminate here, invoice payment calls one of these. */ /* For HTLCs which terminate here, invoice payment calls one of these. */
void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage); void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage);
void fail_htlc(struct htlc_in *hin, enum onion_type failcode); void local_fail_in_htlc(struct htlc_in *hin, const u8 *failmsg TAKES);
/* This json process will be used as the serialize method for /* This json process will be used as the serialize method for
* forward_event_notification_gen and be used in * forward_event_notification_gen and be used in
* `listforwardings_add_forwardings()`. */ * `listforwardings_add_forwardings()`. */
void json_format_forwarding_object(struct json_stream *response, const char *fieldname, void json_format_forwarding_object(struct json_stream *response, const char *fieldname,
const struct forwarding *cur); const struct forwarding *cur);
/* Helper to create (common) WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS */
const u8 *failmsg_incorrect_or_unknown(const tal_t *ctx,
const struct htlc_in *hin);
#endif /* LIGHTNING_LIGHTNINGD_PEER_HTLCS_H */ #endif /* LIGHTNING_LIGHTNINGD_PEER_HTLCS_H */

View File

@ -92,6 +92,10 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx UNNEEDED,
const struct chainparams *chainparams UNNEEDED, const struct chainparams *chainparams UNNEEDED,
const u8 *scriptPubkey UNNEEDED) const u8 *scriptPubkey UNNEEDED)
{ fprintf(stderr, "encode_scriptpubkey_to_addr called!\n"); abort(); } { fprintf(stderr, "encode_scriptpubkey_to_addr called!\n"); abort(); }
/* Generated stub for failmsg_incorrect_or_unknown */
const u8 *failmsg_incorrect_or_unknown(const tal_t *ctx UNNEEDED,
const struct htlc_in *hin UNNEEDED)
{ fprintf(stderr, "failmsg_incorrect_or_unknown called!\n"); abort(); }
/* Generated stub for fatal */ /* Generated stub for fatal */
void fatal(const char *fmt UNNEEDED, ...) void fatal(const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "fatal called!\n"); abort(); } { fprintf(stderr, "fatal called!\n"); abort(); }
@ -147,7 +151,7 @@ bool htlc_is_trimmed(enum side htlc_owner UNNEEDED,
enum side side UNNEEDED) enum side side UNNEEDED)
{ fprintf(stderr, "htlc_is_trimmed called!\n"); abort(); } { fprintf(stderr, "htlc_is_trimmed called!\n"); abort(); }
/* Generated stub for htlc_set_fail */ /* Generated stub for htlc_set_fail */
void htlc_set_fail(struct htlc_set *set UNNEEDED, enum onion_type failcode UNNEEDED) void htlc_set_fail(struct htlc_set *set UNNEEDED, const u8 *failmsg TAKES UNNEEDED)
{ fprintf(stderr, "htlc_set_fail called!\n"); abort(); } { fprintf(stderr, "htlc_set_fail called!\n"); abort(); }
/* Generated stub for htlc_set_fulfill */ /* Generated stub for htlc_set_fulfill */
void htlc_set_fulfill(struct htlc_set *set UNNEEDED, const struct preimage *preimage UNNEEDED) void htlc_set_fulfill(struct htlc_set *set UNNEEDED, const struct preimage *preimage UNNEEDED)
@ -436,6 +440,9 @@ u8 *towire_hsm_sign_invoice(const tal_t *ctx UNNEEDED, const u8 *u5bytes UNNEEDE
/* Generated stub for towire_onchain_dev_memleak */ /* Generated stub for towire_onchain_dev_memleak */
u8 *towire_onchain_dev_memleak(const tal_t *ctx UNNEEDED) u8 *towire_onchain_dev_memleak(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "towire_onchain_dev_memleak called!\n"); abort(); } { fprintf(stderr, "towire_onchain_dev_memleak called!\n"); abort(); }
/* Generated stub for towire_temporary_node_failure */
u8 *towire_temporary_node_failure(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "towire_temporary_node_failure called!\n"); abort(); }
/* Generated stub for txfilter_add_scriptpubkey */ /* Generated stub for txfilter_add_scriptpubkey */
void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED) void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED)
{ fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); } { fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); }