Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invoice hook #2540

Merged
merged 3 commits into from Apr 12, 2019
Merged

invoice hook #2540

Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- JSON API: `listpeers` status now shows how many confirmations until channel is open (#2405)
- Config: Adds parameter `min-capacity-sat` to reject tiny channels.
- JSON API: `listforwards` now includes the time an HTLC was received and when it was resolved. Both are expressed as UNIX timestamps to facilitate parsing (Issue [#2491](https://github.com/ElementsProject/lightning/issues/2491), PR [#2528](https://github.com/ElementsProject/lightning/pull/2528))
- JSON API: new plugin `invoice_payment` hook for intercepting invoices before they're paid.

### Changed

@@ -25,12 +25,6 @@ int test_printf(const char *format, ...);
#undef main

/* AUTOGENERATED MOCKS START */
/* Generated stub for amount_sat_eq */
bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED)
{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); }
/* Generated stub for amount_sat_less */
bool amount_sat_less(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED)
{ fprintf(stderr, "amount_sat_less called!\n"); abort(); }
/* Generated stub for version_and_exit */
char *version_and_exit(const void *unused UNNEEDED)
{ fprintf(stderr, "version_and_exit called!\n"); abort(); }
@@ -288,5 +288,26 @@ It is currently extremely restricted:
Any response but "true" will cause lightningd to error without
committing to the database!

#### `invoice_payment`

This hook is called whenever a valid payment for an unpaid invoice has arrived.

```json
{
"payment": {
"label": "unique-label-for-invoice",
"preimage": "0000000000000000000000000000000000000000000000000000000000000000",
"msat": "10000msat"
}
}
```

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
to accept the payment.

[jsonrpc-spec]: https://www.jsonrpc.org/specification
[jsonrpc-notification-spec]: https://www.jsonrpc.org/specification#notification
[bolt4-failure-codes]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages
@@ -26,6 +26,8 @@
#include <lightningd/log.h>
#include <lightningd/options.h>
#include <lightningd/peer_control.h>
#include <lightningd/peer_htlcs.h>
#include <lightningd/plugin_hook.h>
#include <lightningd/subd.h>
#include <sodium/randombytes.h>
#include <wire/wire_sync.h>
@@ -98,6 +100,175 @@ static void wait_on_invoice(const struct invoice *invoice, void *cmd)
tell_waiter_deleted((struct command *) cmd);
}

struct invoice_payment_hook_payload {
struct lightningd *ld;
/* Set to NULL if it is deleted while waiting for plugin */
struct htlc_in *hin;
/* What invoice it's trying to pay. */
const struct json_escaped *label;
/* Amount it's offering. */
struct amount_msat msat;
/* Preimage we'll give it if succeeds. */
struct preimage preimage;
/* FIXME: Include raw payload! */
};

static void
invoice_payment_serialize(struct invoice_payment_hook_payload *payload,
struct json_stream *stream)
{
json_object_start(stream, "payment");
json_add_escaped_string(stream, "label", payload->label);
json_add_hex(stream, "preimage",
&payload->preimage, sizeof(payload->preimage));
json_add_string(stream, "msat",
type_to_string(tmpctx, struct amount_msat,
&payload->msat));
json_object_end(stream); /* .payment */
}

/* We cheat and return 0 (not a valid onion_type) for "OK" */
static enum onion_type
invoice_payment_deserialize(const tal_t *ctx, const char *buffer,
const jsmntok_t *toks)
{
const jsmntok_t *resulttok, *t;
unsigned int val;

resulttok = json_get_member(buffer, toks, "result");
if (!resulttok)
fatal("Invalid invoice_payment_hook response: %.*s",
toks[0].end - toks[1].start, buffer);

t = json_get_member(buffer, resulttok, "failure_code");
if (!t)
return 0;

if (!json_to_number(buffer, t, &val))
fatal("Invalid invoice_payment_hook failure_code: %.*s",
toks[0].end - toks[1].start, buffer);

/* UPDATE isn't valid for final nodes to return, and I think we
* assert elsewhere that we don't do this! */

This comment has been minimized.

Copy link
@cdecker

cdecker Apr 11, 2019

Member

Yep, just stumbled over the check before 👍

if (val & UPDATE)
fatal("Invalid invoice_payment_hook UPDATE failure_code: %.*s",
toks[0].end - toks[1].start, buffer);

return val;
}

/* Peer dies? Remove hin ptr from payload so we know to ignore plugin return */
static void invoice_payload_remove_hin(struct htlc_in *hin,
struct invoice_payment_hook_payload *payload)
{
assert(payload->hin == hin);
payload->hin = NULL;
}

static void
invoice_payment_hook_cb(struct invoice_payment_hook_payload *payload,
enum onion_type failcode)
{
struct lightningd *ld = payload->ld;
struct invoice invoice;

tal_del_destructor2(payload->hin, invoice_payload_remove_hin, payload);
/* We want to free this, whatever happens. */
tal_steal(tmpctx, payload);

/* If peer dies or something, this can happen. */
if (!payload->hin) {
log_debug(ld->log, "invoice '%s' paying htlc_in has gone!",
payload->label->s);
return;
}

/* If invoice gets paid meanwhile (plugin responds out-of-order?) then
* we can also fail */
if (!wallet_invoice_find_by_label(ld->wallet, &invoice, payload->label)) {
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS;
fail_htlc(payload->hin, failcode);
return;
}

if (failcode != 0) {
fail_htlc(payload->hin, failcode);
return;
}

log_info(ld->log, "Resolved invoice '%s' with amount %s",
payload->label->s,
type_to_string(tmpctx, struct amount_msat, &payload->msat));
wallet_invoice_resolve(ld->wallet, invoice, payload->msat);
fulfill_htlc(payload->hin, &payload->preimage);
}

REGISTER_PLUGIN_HOOK(invoice_payment,
invoice_payment_hook_cb,
struct invoice_payment_hook_payload *,
invoice_payment_serialize,
struct invoice_payment_hook_payload *,
invoice_payment_deserialize,
enum onion_type);

void invoice_try_pay(struct lightningd *ld,
struct htlc_in *hin,
const struct sha256 *payment_hash,
const struct amount_msat msat)
{
struct invoice invoice;
const struct invoice_details *details;
struct invoice_payment_hook_payload *payload;

if (!wallet_invoice_find_unpaid(ld->wallet, &invoice, payment_hash)) {
fail_htlc(hin, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS);
return;
}
details = wallet_invoice_details(tmpctx, ld->wallet, invoice);

/* BOLT #4:
*
* An _intermediate hop_ MUST NOT, but the _final node_:
*...
* - if the amount paid is less than the amount expected:
* - MUST fail the HTLC.
*/
if (details->msat != NULL) {
struct amount_msat twice;

if (amount_msat_less(msat, *details->msat)) {
fail_htlc(hin,
WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS);
return;
}

if (amount_msat_add(&twice, *details->msat, *details->msat)
&& amount_msat_greater(msat, twice)) {
/* FIXME: bolt update fixes this quote! */
/* BOLT #4:
*
* - if the amount paid is more than twice the amount expected:
* - SHOULD fail the HTLC.
* - SHOULD return an `incorrect_or_unknown_payment_details` error.
*/
fail_htlc(hin,
WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS);
return;
}
}

payload = tal(ld, struct invoice_payment_hook_payload);
payload->ld = ld;
payload->label = tal_steal(payload, details->label);
payload->msat = msat;
payload->preimage = details->r;
payload->hin = hin;
tal_add_destructor2(hin, invoice_payload_remove_hin, payload);

log_debug(ld->log, "Calling hook for invoice '%s'", details->label->s);
plugin_hook_call_invoice_payment(ld, payload, payload);
}

static bool hsm_sign_b11(const u5 *u5bytes,
const u8 *hrpu8,
secp256k1_ecdsa_recoverable_signature *rsig,
@@ -1,9 +1,25 @@
#ifndef LIGHTNING_LIGHTNINGD_INVOICE_H
#define LIGHTNING_LIGHTNINGD_INVOICE_H
#include "config.h"
#include <bitcoin/preimage.h>
#include <ccan/crypto/sha256/sha256.h>
#include <ccan/list/list.h>
#include <ccan/tal/tal.h>
#include <wire/gen_onion_wire.h>

struct amount_msat;
struct htlc_in;
struct lightningd;
struct sha256;

/**
* invoice_try_pay - process payment for this payment_hash, amount msat.
* @ld: lightningd
* @hin: the input HTLC which is offering to pay.
* @payment_hash: hash of preimage they want.
* @msat: amount they offer to pay.
*
* Either calls fulfill_htlc() or fail_htlcs().
*/
void invoice_try_pay(struct lightningd *ld,
struct htlc_in *hin,
const struct sha256 *payment_hash,
const struct amount_msat msat);

#endif /* LIGHTNING_LIGHTNINGD_INVOICE_H */
@@ -135,6 +135,14 @@ static void local_fail_htlc(struct htlc_in *hin, enum onion_type failcode,
fail_in_htlc(hin, failcode, NULL, out_channel);
}

void fail_htlc(struct htlc_in *hin, enum onion_type failcode)
{
assert(failcode);
/* Final hop never sends an UPDATE. */
assert(!(failcode & UPDATE));
local_fail_htlc(hin, failcode, NULL);
}

/* localfail are for handing to the local payer if it's local. */
static void fail_out_htlc(struct htlc_out *hout, const char *localfail)
{
@@ -216,12 +224,19 @@ static bool check_cltv(struct htlc_in *hin,
return false;
}

static void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage)
void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage)
{
u8 *msg;
struct channel *channel = hin->key.channel;
struct wallet *wallet = channel->peer->ld->wallet;

if (hin->hstate != RCVD_ADD_ACK_REVOCATION) {

This comment has been minimized.

Copy link
@cdecker

cdecker Apr 11, 2019

Member

I assume this may happen due to the updates no longer being atomic?

This comment has been minimized.

Copy link
@rustyrussell

rustyrussell Apr 12, 2019

Author Contributor

Yes, in particular we might have gone onchain while waiting, then timed out the HTLC.

log_debug(channel->log,
"HTLC fulfilled, but not ready any more (%s).",
htlc_state_name(hin->hstate));
return;
}

hin->preimage = tal_dup(hin, struct preimage, preimage);

/* We update state now to signal it's in progress, for persistence. */
@@ -259,8 +274,6 @@ static void handle_localpay(struct htlc_in *hin,
u32 outgoing_cltv_value)
{
enum onion_type failcode;
struct invoice invoice;
const struct invoice_details *details;
struct lightningd *ld = hin->key.channel->peer->ld;

/* BOLT #4:
@@ -289,41 +302,6 @@ static void handle_localpay(struct htlc_in *hin,
goto fail;
}

if (!wallet_invoice_find_unpaid(ld->wallet, &invoice, payment_hash)) {
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS;
goto fail;
}
details = wallet_invoice_details(tmpctx, ld->wallet, invoice);

/* BOLT #4:
*
* An _intermediate hop_ MUST NOT, but the _final node_:
*...
* - if the amount paid is less than the amount expected:
* - MUST fail the HTLC.
*/
if (details->msat != NULL) {
struct amount_msat twice;

if (amount_msat_less(hin->msat, *details->msat)) {
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS;
goto fail;
}

if (amount_msat_add(&twice, *details->msat, *details->msat)
&& amount_msat_greater(hin->msat, twice)) {
/* FIXME: bolt update fixes this quote! */
/* BOLT #4:
*
* - if the amount paid is more than twice the amount expected:
* - SHOULD fail the HTLC.
* - SHOULD return an `incorrect_or_unknown_payment_details` error.
*/
failcode = WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS;
goto fail;
}
}

/* BOLT #4:
*
* - if the `cltv_expiry` value is unreasonably near the present:
@@ -341,21 +319,11 @@ static void handle_localpay(struct htlc_in *hin,
goto fail;
}

log_info(ld->log, "Resolving invoice '%s' with HTLC %"PRIu64,
details->label->s, hin->key.id);
log_debug(ld->log, "%s: Actual amount %s, HTLC expiry %u",
details->label->s,
type_to_string(tmpctx, struct amount_msat, &hin->msat),
cltv_expiry);
fulfill_htlc(hin, &details->r);
wallet_invoice_resolve(ld->wallet, invoice, hin->msat);

invoice_try_pay(ld, hin, payment_hash, amt_to_forward);
return;

fail:
/* Final hop never sends an UPDATE. */
assert(!(failcode & UPDATE));
local_fail_htlc(hin, failcode, NULL);
fail_htlc(hin, failcode);
}

/*
@@ -63,4 +63,8 @@ void htlcs_reconnect(struct lightningd *ld,
struct htlc_in_map *htlcs_in,
struct htlc_out_map *htlcs_out);

/* For HTLCs which terminate here, invoice payment calls one of these. */
void fulfill_htlc(struct htlc_in *hin, const struct preimage *preimage);
void fail_htlc(struct htlc_in *hin, enum onion_type failcode);

#endif /* LIGHTNING_LIGHTNINGD_PEER_HTLCS_H */
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.