Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/developers-guide/plugin-development/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ It can also specify `forward_to` in the response, replacing the destination. Th

Also, it can specify `extra_tlvs` in the response. This will replace the TLV-stream `update_add_htlc_tlvs` in the `update_add_htlc` message for forwarded htlcs.

If the node is the final destination, the plugin can also replace the amount of the invoice that belongs to the `payment_hash` by specifying `invoice_msat`.

```json
{
"result": "fail",
Expand Down
6 changes: 4 additions & 2 deletions lightningd/htlc_set.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ void htlc_set_add_(struct lightningd *ld,
struct logger *log,
struct amount_msat msat,
struct amount_msat total_msat,
const struct amount_msat *invoice_msat_override,
const struct sha256 *payment_hash,
const struct secret *payment_secret,
void (*fail)(void *, const u8 *),
Expand All @@ -150,8 +151,9 @@ void htlc_set_add_(struct lightningd *ld,
* [Failure Messages](#failure-messages)
* - Note: "amount paid" specified there is the `total_msat` field.
*/
details = invoice_check_payment(tmpctx, ld, payment_hash,
total_msat, payment_secret, &err);
details = invoice_check_payment(tmpctx, ld, payment_hash, total_msat,
invoice_msat_override, payment_secret,
&err);
if (!details) {
log_debug(log, "payment failed: %s", err);
fail(arg, take(failmsg_incorrect_or_unknown(NULL, ld, msat)));
Expand Down
8 changes: 5 additions & 3 deletions lightningd/htlc_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,17 @@ void htlc_set_add_(struct lightningd *ld,
struct logger *log,
struct amount_msat msat,
struct amount_msat total_msat,
const struct amount_msat *invoice_msat_override,
const struct sha256 *payment_hash,
const struct secret *payment_secret,
void (*fail)(void *, const u8 *),
void (*succeeded)(void *, const struct preimage *),
void *arg);

#define htlc_set_add(ld, log, msat, total_msat, payment_hash, payment_secret, \
fail, succeeded, arg) \
htlc_set_add_((ld), (log), (msat), (total_msat), (payment_hash), \
#define htlc_set_add(ld, log, msat, total_msat, invoice_msat_override, \
payment_hash, payment_secret, fail, succeeded, arg)\
htlc_set_add_((ld), (log), (msat), (total_msat), \
(invoice_msat_override), (payment_hash), \
(payment_secret), \
typesafe_cb_postargs(void, void *, \
(fail), (arg), \
Expand Down
11 changes: 8 additions & 3 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ invoice_check_payment(const tal_t *ctx,
struct lightningd *ld,
const struct sha256 *payment_hash,
const struct amount_msat msat,
const struct amount_msat *expected_msat_override,
const struct secret *payment_secret,
const char **err)
{
Expand Down Expand Up @@ -408,15 +409,19 @@ invoice_check_payment(const tal_t *ctx,
if (details->msat != NULL) {
struct amount_msat twice;

if (amount_msat_less(msat, *details->msat)) {
/* Override the expected amount. */
struct amount_msat expected_msat =
expected_msat_override ? *expected_msat_override : *details->msat;

if (amount_msat_less(msat, expected_msat)) {
*err = tal_fmt(ctx, "Attempt to pay %s with amount %s < %s",
fmt_sha256(tmpctx, &details->rhash),
fmt_amount_msat(tmpctx, msat),
fmt_amount_msat(tmpctx, *details->msat));
fmt_amount_msat(tmpctx, expected_msat));
return tal_free(details);
}

if (amount_msat_add(&twice, *details->msat, *details->msat)
if (amount_msat_add(&twice, expected_msat, expected_msat)
&& amount_msat_greater(msat, twice)) {
*err = tal_fmt(ctx, "Attempt to pay %s with amount %s > %s",
fmt_sha256(tmpctx, &details->rhash),
Expand Down
2 changes: 2 additions & 0 deletions lightningd/invoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct invoice_details {
* @ld: lightningd
* @payment_hash: hash of preimage they want.
* @msat: amount they offer to pay.
* @expected_msat_override: if set: overrides the amount we expect to be payed.
* @payment_secret: they payment secret they sent, if any.
* @err: error string if it returns NULL.
*
Expand All @@ -59,6 +60,7 @@ const struct invoice_details *invoice_check_payment(const tal_t *ctx,
struct lightningd *ld,
const struct sha256 *payment_hash,
const struct amount_msat msat,
const struct amount_msat *expected_msat_override,
const struct secret *payment_secret,
const char **err);

Expand Down
4 changes: 2 additions & 2 deletions lightningd/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -1482,7 +1482,7 @@ static struct command_result *self_payment(struct lightningd *ld,
local_invreq_id);

/* Now, resolve the invoice */
inv = invoice_check_payment(tmpctx, ld, rhash, msat, payment_secret, &err);
inv = invoice_check_payment(tmpctx, ld, rhash, msat, NULL, payment_secret, &err);
if (!inv) {
struct routing_failure *fail;
wallet_payment_set_status(ld->wallet, rhash, partid, groupid,
Expand Down Expand Up @@ -1973,7 +1973,7 @@ static struct command_result *json_injectpaymentonion(struct command *cmd,
* not resolve immediately */
fixme_ignore(command_still_pending(cmd));
htlc_set_add(cmd->ld, cmd->ld->log, *msat, *payload->total_msat,
payment_hash, payload->payment_secret,
NULL, payment_hash, payload->payment_secret,
selfpay_mpp_fail, selfpay_mpp_succeeded,
selfpay);
return command_its_complicated("htlc_set_add may have immediately succeeded or failed");
Expand Down
27 changes: 26 additions & 1 deletion lightningd/peer_htlcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <channeld/channeld_wiregen.h>
#include <common/amount.h>
#include <common/blinding.h>
#include <common/ecdh.h>
#include <common/json_command.h>
#include <common/json_parse.h>
#include <common/onion_decode.h>
#include <common/onionreply.h>
#include <common/timeout.h>
Expand Down Expand Up @@ -435,6 +437,7 @@ static void handle_localpay(struct htlc_in *hin,
struct amount_msat amt_to_forward,
u32 outgoing_cltv_value,
struct amount_msat total_msat,
const struct amount_msat *invoice_msat_override,
const struct secret *payment_secret,
const u8 *payment_metadata)
{
Expand Down Expand Up @@ -525,6 +528,7 @@ static void handle_localpay(struct htlc_in *hin,

htlc_set_add(ld, hin->key.channel->log,
hin->msat, total_msat,
invoice_msat_override,
&hin->payment_hash,
payment_secret,
local_fail_in_htlc,
Expand Down Expand Up @@ -947,6 +951,9 @@ struct htlc_accepted_hook_payload {
size_t failtlvpos;
const char *failexplanation;
u8 *extra_tlvs_raw;
/* Default is NULL, if NOT NULL: used to override the amount of the
* invoice this htlc belongs to in checks! */
struct amount_msat *invoice_msat;
};

static void
Expand Down Expand Up @@ -1003,7 +1010,8 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re
struct htlc_in *hin = request->hin;
struct lightningd *ld = request->ld;
struct preimage payment_preimage;
const jsmntok_t *resulttok, *paykeytok, *payloadtok, *fwdtok, *extra_tlvs_tok;
const jsmntok_t *resulttok, *paykeytok, *payloadtok, *fwdtok, *extra_tlvs_tok,
*invmsattok;
u8 *failonion, *raw_tlvs;

if (!toks || !buffer)
Expand All @@ -1018,6 +1026,17 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re
json_strdup(tmpctx, buffer, toks));
}

invmsattok = json_get_member(buffer, toks, "invoice_msat");
if (invmsattok) {
tal_free(request->invoice_msat);
request->invoice_msat = tal(request, struct amount_msat);
if (!json_to_msat(buffer, invmsattok, request->invoice_msat)) {
fatal("Bad invoice_msat for htlc_accepted hook: %.*s",
invmsattok->end - invmsattok->start,
buffer + invmsattok->start);
}
}

extra_tlvs_tok = json_get_member(buffer, toks, "extra_tlvs");
if (extra_tlvs_tok) {
size_t max;
Expand Down Expand Up @@ -1275,6 +1294,7 @@ htlc_accepted_hook_final(struct htlc_accepted_hook_payload *request STEALS)
request->payload->amt_to_forward,
request->payload->outgoing_cltv,
*request->payload->total_msat,
request->invoice_msat,
request->payload->payment_secret,
request->payload->payment_metadata);

Expand Down Expand Up @@ -1555,6 +1575,11 @@ static bool peer_accepted_htlc(const tal_t *ctx,
hook_payload->extra_tlvs_raw = NULL;
}

/* We don't set the invoice amount here, if it is set during a hook
* response, it will be used to override the actual invoice amount on
* later checks. */
hook_payload->invoice_msat = NULL;

plugin_hook_call_htlc_accepted(ld, NULL, hook_payload);

/* Falling through here is ok, after all the HTLC locked */
Expand Down
29 changes: 29 additions & 0 deletions tests/plugins/override_invoice_msat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""A plugin that overrides the amount of the invoice that belongs to an HTLC."""

from pyln.client import Plugin


plugin = Plugin()


@plugin.hook("htlc_accepted")
def on_htlc_accepted(htlc, onion, plugin, **kwargs):
res = {"result": "continue"}
if plugin.invoice_msat:
res["invoice_msat"] = plugin.invoice_msat
return res


@plugin.method("setinvoicemsat")
def setinvoicemsat(plugin, msat: int):
"""Sets invoice_msat for the htlc_accepted response."""
plugin.invoice_msat = msat


@plugin.init()
def on_init(**kwargs):
plugin.invoice_msat = None


plugin.run()
29 changes: 29 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -7133,3 +7133,32 @@ def test_htlc_tlv_crash(node_factory):

l1.rpc.waitsendpay(inv1['payment_hash'], TIMEOUT)
l1.rpc.waitsendpay(inv2['payment_hash'], TIMEOUT)


def test_invoice_amount_override(node_factory):
"""Uses the htlc_accepted hook response value `invoice_msat` to override
the expected total payment amount of the invoice.
"""
plugin = os.path.join(os.path.dirname(__file__), "plugins/override_invoice_msat.py")
l1, l2 = node_factory.line_graph(2, opts=[{}, {"plugin": plugin}])

inv = l2.rpc.invoice(10_000, "expected_amt_override", "expected_amt_override")

route = [
{
"amount_msat": 1_000, # Reduced amount that is below the expected amount
"id": l2.info["id"],
"delay": 10,
"channel": first_scid(l1, l2),
}
]

with pytest.raises(RpcError):
l1.rpc.sendpay(route, inv["payment_hash"], payment_secret=inv["payment_secret"])
l1.rpc.waitsendpay(inv["payment_hash"])

# Override expected invoice amount, via htlc_accepted.
l2.rpc.setinvoicemsat(msat=1_000)

l1.rpc.sendpay(route, inv["payment_hash"], payment_secret=inv["payment_secret"])
assert l1.rpc.waitsendpay(inv["payment_hash"])["status"] == "complete"
2 changes: 2 additions & 0 deletions wallet/test/run-wallet.c
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ void htlc_set_add_(struct lightningd *ld UNNEEDED,
struct logger *log UNNEEDED,
struct amount_msat msat UNNEEDED,
struct amount_msat total_msat UNNEEDED,
const struct amount_msat *invoice_msat_override UNNEEDED,
const struct sha256 *payment_hash UNNEEDED,
const struct secret *payment_secret UNNEEDED,
void (*fail)(void * UNNEEDED, const u8 *) UNNEEDED,
Expand All @@ -391,6 +392,7 @@ const struct invoice_details *invoice_check_payment(const tal_t *ctx UNNEEDED,
struct lightningd *ld UNNEEDED,
const struct sha256 *payment_hash UNNEEDED,
const struct amount_msat msat UNNEEDED,
const struct amount_msat *expected_msat_override UNNEEDED,
const struct secret *payment_secret UNNEEDED,
const char **err UNNEEDED)
{ fprintf(stderr, "invoice_check_payment called!\n"); abort(); }
Expand Down
Loading