mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-19 05:44:12 +01:00
lightningd: implement invoice hook.
This also allows plugins to do "hold invoices" a-la LND, useful for just-in-time inventory handling. We're careful to handle the invoice getting paid behind our backs, and the incoming HTLC going away. Once @cdecker's sphinx rework is in, we can also hand the raw payload to the invoice_payment_hook, for special effects. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
6630b99cf7
commit
43d07aaed2
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -29,6 +29,7 @@
|
||||
#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>
|
||||
@ -101,6 +102,117 @@ 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! */
|
||||
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,
|
||||
@ -108,6 +220,7 @@ void invoice_try_pay(struct lightningd *ld,
|
||||
{
|
||||
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);
|
||||
@ -146,11 +259,16 @@ void invoice_try_pay(struct lightningd *ld,
|
||||
}
|
||||
}
|
||||
|
||||
log_info(ld->log, "Resolved invoice '%s' with amount %s",
|
||||
details->label->s,
|
||||
type_to_string(tmpctx, struct amount_msat, &msat));
|
||||
wallet_invoice_resolve(ld->wallet, invoice, msat);
|
||||
fulfill_htlc(hin, &details->r);
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user