You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1596 lines
48 KiB
1596 lines
48 KiB
#include "invoice.h"
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/asort/asort.h>
|
|
#include <ccan/json_escape/json_escape.h>
|
|
#include <ccan/str/hex/hex.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/amount.h>
|
|
#include <common/bech32.h>
|
|
#include <common/bolt11.h>
|
|
#include <common/bolt11_json.h>
|
|
#if EXPERIMENTAL_FEATURES
|
|
#include <common/bolt12.h>
|
|
#include <common/bolt12_merkle.h>
|
|
#endif
|
|
#include <common/configdir.h>
|
|
#include <common/features.h>
|
|
#include <common/json_command.h>
|
|
#include <common/json_helpers.h>
|
|
#include <common/jsonrpc_errors.h>
|
|
#include <common/overflows.h>
|
|
#include <common/param.h>
|
|
#include <common/random_select.h>
|
|
#include <common/timeout.h>
|
|
#include <common/utils.h>
|
|
#include <errno.h>
|
|
#include <gossipd/gossipd_wiregen.h>
|
|
#include <hsmd/hsmd_wiregen.h>
|
|
#include <inttypes.h>
|
|
#include <lightningd/channel.h>
|
|
#include <lightningd/hsm_control.h>
|
|
#include <lightningd/json.h>
|
|
#include <lightningd/jsonrpc.h>
|
|
#include <lightningd/lightningd.h>
|
|
#include <lightningd/log.h>
|
|
#include <lightningd/notification.h>
|
|
#include <lightningd/options.h>
|
|
#include <lightningd/peer_control.h>
|
|
#include <lightningd/peer_htlcs.h>
|
|
#include <lightningd/plugin_hook.h>
|
|
#include <lightningd/routehint.h>
|
|
#include <lightningd/subd.h>
|
|
#include <sodium/randombytes.h>
|
|
#include <wire/wire_sync.h>
|
|
|
|
static const char *invoice_status_str(const struct invoice_details *inv)
|
|
{
|
|
if (inv->state == PAID)
|
|
return "paid";
|
|
if (inv->state == EXPIRED)
|
|
return "expired";
|
|
return "unpaid";
|
|
}
|
|
|
|
static void json_add_invoice(struct json_stream *response,
|
|
const struct invoice_details *inv)
|
|
{
|
|
json_add_escaped_string(response, "label", inv->label);
|
|
json_add_invstring(response, inv->invstring);
|
|
json_add_sha256(response, "payment_hash", &inv->rhash);
|
|
if (inv->msat)
|
|
json_add_amount_msat_compat(response, *inv->msat,
|
|
"msatoshi", "amount_msat");
|
|
json_add_string(response, "status", invoice_status_str(inv));
|
|
if (inv->state == PAID) {
|
|
json_add_u64(response, "pay_index", inv->pay_index);
|
|
json_add_amount_msat_compat(response, inv->received,
|
|
"msatoshi_received",
|
|
"amount_received_msat");
|
|
json_add_u64(response, "paid_at", inv->paid_timestamp);
|
|
json_add_preimage(response, "payment_preimage", &inv->r);
|
|
}
|
|
if (inv->description)
|
|
json_add_string(response, "description", inv->description);
|
|
|
|
json_add_u64(response, "expires_at", inv->expiry_time);
|
|
if (inv->local_offer_id)
|
|
json_add_sha256(response, "local_offer_id", inv->local_offer_id);
|
|
}
|
|
|
|
static struct command_result *tell_waiter(struct command *cmd,
|
|
const struct invoice *inv)
|
|
{
|
|
struct json_stream *response;
|
|
const struct invoice_details *details;
|
|
|
|
details = wallet_invoice_details(cmd, cmd->ld->wallet, *inv);
|
|
if (details->state == PAID) {
|
|
response = json_stream_success(cmd);
|
|
json_add_invoice(response, details);
|
|
return command_success(cmd, response);
|
|
} else {
|
|
response = json_stream_fail(cmd, INVOICE_EXPIRED_DURING_WAIT,
|
|
"invoice expired during wait");
|
|
json_add_invoice(response, details);
|
|
json_object_end(response);
|
|
return command_failed(cmd, response);
|
|
}
|
|
}
|
|
|
|
static void tell_waiter_deleted(struct command *cmd)
|
|
{
|
|
was_pending(command_fail(cmd, LIGHTNINGD,
|
|
"Invoice deleted during wait"));
|
|
}
|
|
static void wait_on_invoice(const struct invoice *invoice, void *cmd)
|
|
{
|
|
if (invoice)
|
|
tell_waiter((struct command *) cmd, invoice);
|
|
else
|
|
tell_waiter_deleted((struct command *) cmd);
|
|
}
|
|
static void wait_timed_out(struct command *cmd)
|
|
{
|
|
was_pending(command_fail(cmd, INVOICE_WAIT_TIMED_OUT,
|
|
"Timed out while waiting "
|
|
"for invoice to be paid"));
|
|
}
|
|
|
|
/* We derive invoice secret using 1-way function from payment_preimage
|
|
* (just a different one from the payment_hash!) */
|
|
static void invoice_secret(const struct preimage *payment_preimage,
|
|
struct secret *payment_secret)
|
|
{
|
|
struct preimage modified;
|
|
struct sha256 secret;
|
|
|
|
modified = *payment_preimage;
|
|
modified.r[0] ^= 1;
|
|
|
|
sha256(&secret, modified.r,
|
|
ARRAY_SIZE(modified.r) * sizeof(*modified.r));
|
|
BUILD_ASSERT(sizeof(secret.u.u8) == sizeof(payment_secret->data));
|
|
memcpy(payment_secret->data, secret.u.u8, sizeof(secret.u.u8));
|
|
}
|
|
|
|
#if EXPERIMENTAL_FEATURES
|
|
/* FIXME: This is a hack. The real secret should be a signature of some
|
|
* onion key, using the payer_id */
|
|
static void invoice_secret_bolt12(struct lightningd *ld,
|
|
const char *invstring,
|
|
struct secret *payment_secret)
|
|
{
|
|
char *fail;
|
|
struct tlv_invoice *inv;
|
|
struct sha256 merkle;
|
|
|
|
inv = invoice_decode(tmpctx, invstring, strlen(invstring),
|
|
NULL, NULL, &fail);
|
|
if (!inv) {
|
|
log_broken(ld->log, "Unable to decode our invoice %s",
|
|
invstring);
|
|
return;
|
|
}
|
|
|
|
merkle_tlv(inv->fields, &merkle);
|
|
BUILD_ASSERT(sizeof(*payment_secret) == sizeof(merkle));
|
|
memcpy(payment_secret, &merkle, sizeof(merkle));
|
|
}
|
|
#endif /* EXPERIMENTAL_FEATURES */
|
|
|
|
struct invoice_payment_hook_payload {
|
|
struct lightningd *ld;
|
|
/* Set to NULL if it is deleted while waiting for plugin */
|
|
struct htlc_set *set;
|
|
/* What invoice it's trying to pay. */
|
|
const struct json_escape *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_preimage(stream, "preimage", &payload->preimage);
|
|
json_add_string(stream, "msat",
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
&payload->msat));
|
|
json_object_end(stream); /* .payment */
|
|
}
|
|
|
|
/* Set times out or HTLC deleted? Remove set ptr from payload so we
|
|
* know to ignore plugin return */
|
|
static void invoice_payload_remove_set(struct htlc_set *set,
|
|
struct invoice_payment_hook_payload *payload)
|
|
{
|
|
assert(payload->set == set);
|
|
payload->set = NULL;
|
|
}
|
|
|
|
static const u8 *hook_gives_failmsg(const tal_t *ctx,
|
|
struct lightningd *ld,
|
|
const struct htlc_in *hin,
|
|
const char *buffer,
|
|
const jsmntok_t *toks)
|
|
{
|
|
const jsmntok_t *resulttok;
|
|
const jsmntok_t *t;
|
|
unsigned int val;
|
|
|
|
/* No plugin registered on hook at all? */
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
resulttok = json_get_member(buffer, toks, "result");
|
|
if (resulttok) {
|
|
if (json_tok_streq(buffer, resulttok, "continue")) {
|
|
return NULL;
|
|
} else if (json_tok_streq(buffer, resulttok, "reject")) {
|
|
return failmsg_incorrect_or_unknown(ctx, ld, hin);
|
|
} else
|
|
fatal("Invalid invoice_payment hook result: %.*s",
|
|
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");
|
|
if (!t) {
|
|
static bool warned = false;
|
|
if (!warned) {
|
|
warned = true;
|
|
log_unusual(ld->log,
|
|
"Plugin did not return object with "
|
|
"'result' or 'failure_message' fields. "
|
|
"This is now deprecated and you should "
|
|
"return {'result': 'continue' } or "
|
|
"{'result': 'reject'} or "
|
|
"{'failure_message'... instead.");
|
|
}
|
|
return failmsg_incorrect_or_unknown(ctx, ld, hin);
|
|
}
|
|
|
|
if (!json_to_number(buffer, t, &val))
|
|
fatal("Invalid invoice_payment_hook failure_code: %.*s",
|
|
toks[0].end - toks[1].start, buffer);
|
|
|
|
if (val == WIRE_TEMPORARY_NODE_FAILURE)
|
|
return towire_temporary_node_failure(ctx);
|
|
if (val != WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
|
|
log_broken(hin->key.channel->log,
|
|
"invoice_payment hook returned failcode %u,"
|
|
" changing to incorrect_or_unknown_payment_details",
|
|
val);
|
|
|
|
return failmsg_incorrect_or_unknown(ctx, ld, hin);
|
|
}
|
|
|
|
static void
|
|
invoice_payment_hooks_done(struct invoice_payment_hook_payload *payload STEALS)
|
|
{
|
|
struct invoice invoice;
|
|
struct lightningd *ld = payload->ld;
|
|
|
|
tal_del_destructor2(payload->set, invoice_payload_remove_set, payload);
|
|
/* We want to free this, whatever happens. */
|
|
tal_steal(tmpctx, payload);
|
|
|
|
/* 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)) {
|
|
htlc_set_fail(payload->set, take(failmsg_incorrect_or_unknown(
|
|
NULL, ld, payload->set->htlcs[0])));
|
|
return;
|
|
}
|
|
|
|
/* Paid or expired in the meantime. */
|
|
if (!wallet_invoice_resolve(ld->wallet, invoice, payload->msat)) {
|
|
htlc_set_fail(payload->set, take(failmsg_incorrect_or_unknown(
|
|
NULL, ld, payload->set->htlcs[0])));
|
|
return;
|
|
}
|
|
|
|
log_info(ld->log, "Resolved invoice '%s' with amount %s in %zu htlcs",
|
|
payload->label->s,
|
|
type_to_string(tmpctx, struct amount_msat, &payload->msat),
|
|
tal_count(payload->set->htlcs));
|
|
htlc_set_fulfill(payload->set, &payload->preimage);
|
|
}
|
|
|
|
static bool
|
|
invoice_payment_deserialize(struct invoice_payment_hook_payload *payload,
|
|
const char *buffer,
|
|
const jsmntok_t *toks)
|
|
{
|
|
struct lightningd *ld = payload->ld;
|
|
const u8 *failmsg;
|
|
|
|
/* If peer dies or something, this can happen. */
|
|
if (!payload->set) {
|
|
log_debug(ld->log, "invoice '%s' paying htlc_in has gone!",
|
|
payload->label->s);
|
|
return false;
|
|
}
|
|
|
|
/* Did we have a hook result? */
|
|
failmsg = hook_gives_failmsg(NULL, ld,
|
|
payload->set->htlcs[0], buffer, toks);
|
|
if (failmsg) {
|
|
htlc_set_fail(payload->set, take(failmsg));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
REGISTER_PLUGIN_HOOK(invoice_payment,
|
|
invoice_payment_deserialize,
|
|
invoice_payment_hooks_done,
|
|
invoice_payment_serialize,
|
|
struct invoice_payment_hook_payload *);
|
|
|
|
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 secret *payment_secret)
|
|
{
|
|
struct invoice invoice;
|
|
const struct invoice_details *details;
|
|
|
|
/* BOLT #4:
|
|
* - if the payment hash has already been paid:
|
|
* - MAY treat the payment hash as unknown.
|
|
* - MAY succeed in accepting the HTLC.
|
|
*...
|
|
* - if the payment hash is unknown:
|
|
* - MUST fail the HTLC.
|
|
* - MUST return an `incorrect_or_unknown_payment_details` error.
|
|
*/
|
|
if (!wallet_invoice_find_unpaid(ld->wallet, &invoice, payment_hash)) {
|
|
log_debug(ld->log, "Unknown paid invoice %s",
|
|
type_to_string(tmpctx, struct sha256, payment_hash));
|
|
if (wallet_invoice_find_by_rhash(ld->wallet, &invoice, payment_hash)) {
|
|
log_debug(ld->log, "ALREADY paid invoice %s",
|
|
type_to_string(tmpctx, struct sha256, payment_hash));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
details = wallet_invoice_details(ctx, ld->wallet, invoice);
|
|
|
|
/* BOLT #4:
|
|
* - if the `payment_secret` doesn't match the expected value for that
|
|
* `payment_hash`, or the `payment_secret` is required and is not
|
|
* present:
|
|
* - MUST fail the HTLC.
|
|
*/
|
|
if (feature_is_set(details->features, COMPULSORY_FEATURE(OPT_VAR_ONION))
|
|
&& !payment_secret) {
|
|
log_debug(ld->log, "Attept to pay %s without secret",
|
|
type_to_string(tmpctx, struct sha256, &details->rhash));
|
|
return tal_free(details);
|
|
}
|
|
|
|
if (payment_secret) {
|
|
struct secret expected;
|
|
|
|
#if EXPERIMENTAL_FEATURES
|
|
if (details->invstring && strstarts(details->invstring, "lni1"))
|
|
invoice_secret_bolt12(ld, details->invstring, &expected);
|
|
else
|
|
#endif /* EXPERIMENTAL_FEATURES */
|
|
invoice_secret(&details->r, &expected);
|
|
if (!secret_eq_consttime(payment_secret, &expected)) {
|
|
log_debug(ld->log, "Attept to pay %s with wrong secret",
|
|
type_to_string(tmpctx, struct sha256,
|
|
&details->rhash));
|
|
return tal_free(details);
|
|
}
|
|
}
|
|
|
|
/* 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)) {
|
|
log_debug(ld->log, "Attept to pay %s with amount %s < %s",
|
|
type_to_string(tmpctx, struct sha256,
|
|
&details->rhash),
|
|
type_to_string(tmpctx, struct amount_msat, &msat),
|
|
type_to_string(tmpctx, struct amount_msat, details->msat));
|
|
return tal_free(details);
|
|
}
|
|
|
|
if (amount_msat_add(&twice, *details->msat, *details->msat)
|
|
&& amount_msat_greater(msat, twice)) {
|
|
log_debug(ld->log, "Attept to pay %s with amount %s > %s",
|
|
type_to_string(tmpctx, struct sha256,
|
|
&details->rhash),
|
|
type_to_string(tmpctx, struct amount_msat, details->msat),
|
|
type_to_string(tmpctx, struct amount_msat, &twice));
|
|
/* BOLT #4:
|
|
*
|
|
* - if the amount paid is more than twice the amount
|
|
* expected:
|
|
* - SHOULD fail the HTLC.
|
|
*/
|
|
return tal_free(details);
|
|
}
|
|
}
|
|
return details;
|
|
}
|
|
|
|
void invoice_try_pay(struct lightningd *ld,
|
|
struct htlc_set *set,
|
|
const struct invoice_details *details)
|
|
{
|
|
struct invoice_payment_hook_payload *payload;
|
|
|
|
payload = tal(NULL, struct invoice_payment_hook_payload);
|
|
payload->ld = ld;
|
|
payload->label = tal_steal(payload, details->label);
|
|
payload->msat = set->so_far;
|
|
payload->preimage = details->r;
|
|
payload->set = set;
|
|
tal_add_destructor2(set, invoice_payload_remove_set, payload);
|
|
|
|
notify_invoice_payment(ld, payload->msat, payload->preimage, payload->label);
|
|
|
|
plugin_hook_call_invoice_payment(ld, payload);
|
|
}
|
|
|
|
static bool hsm_sign_b11(const u5 *u5bytes,
|
|
const u8 *hrpu8,
|
|
secp256k1_ecdsa_recoverable_signature *rsig,
|
|
struct lightningd *ld)
|
|
{
|
|
u8 *msg = towire_hsmd_sign_invoice(NULL, u5bytes, hrpu8);
|
|
|
|
if (!wire_sync_write(ld->hsm_fd, take(msg)))
|
|
fatal("Could not write to HSM: %s", strerror(errno));
|
|
|
|
msg = wire_sync_read(tmpctx, ld->hsm_fd);
|
|
if (!fromwire_hsmd_sign_invoice_reply(msg, rsig))
|
|
fatal("HSM gave bad sign_invoice_reply %s",
|
|
tal_hex(msg, msg));
|
|
|
|
return true;
|
|
}
|
|
|
|
#if EXPERIMENTAL_FEATURES
|
|
static void hsm_sign_b12_invoice(struct lightningd *ld,
|
|
struct tlv_invoice *invoice)
|
|
{
|
|
struct sha256 merkle;
|
|
u8 *msg;
|
|
|
|
assert(!invoice->signature);
|
|
|
|
merkle_tlv(invoice->fields, &merkle);
|
|
msg = towire_hsmd_sign_bolt12(NULL, "invoice", "signature", &merkle, NULL);
|
|
|
|
if (!wire_sync_write(ld->hsm_fd, take(msg)))
|
|
fatal("Could not write to HSM: %s", strerror(errno));
|
|
|
|
msg = wire_sync_read(tmpctx, ld->hsm_fd);
|
|
invoice->signature = tal(invoice, struct bip340sig);
|
|
if (!fromwire_hsmd_sign_bolt12_reply(msg, invoice->signature))
|
|
fatal("HSM gave bad sign_invoice_reply %s",
|
|
tal_hex(msg, msg));
|
|
}
|
|
#endif /* EXPERIMENTAL_FEATURES */
|
|
|
|
static struct command_result *parse_fallback(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *fallback,
|
|
const u8 **fallback_script)
|
|
|
|
{
|
|
enum address_parse_result fallback_parse;
|
|
|
|
fallback_parse
|
|
= json_to_address_scriptpubkey(cmd,
|
|
chainparams,
|
|
buffer, fallback,
|
|
fallback_script);
|
|
if (fallback_parse == ADDRESS_PARSE_UNRECOGNIZED) {
|
|
return command_fail(cmd, LIGHTNINGD,
|
|
"Fallback address not valid");
|
|
} else if (fallback_parse == ADDRESS_PARSE_WRONG_NETWORK) {
|
|
return command_fail(cmd, LIGHTNINGD,
|
|
"Fallback address does not match our network %s",
|
|
chainparams->network_name);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* From array of incoming channels [inchan], find suitable ones for
|
|
* a payment-to-us of [amount_needed], using criteria:
|
|
* 1. Channel's peer is known, in state CHANNELD_NORMAL and is online.
|
|
* 2. Channel's peer capacity to pay us is sufficient.
|
|
*
|
|
* Then use weighted reservoir sampling, which makes probing channel balances
|
|
* harder, to choose one channel from the set of suitable channels. It favors
|
|
* channels that have less balance on our side as fraction of their capacity.
|
|
*/
|
|
static struct route_info **select_inchan(const tal_t *ctx,
|
|
struct lightningd *ld,
|
|
struct amount_msat amount_needed,
|
|
const struct routehint_candidate
|
|
*candidates)
|
|
{
|
|
/* BOLT11 struct wants an array of arrays (can provide multiple routes) */
|
|
struct route_info **r = NULL;
|
|
double total_weight = 0.0;
|
|
|
|
/* Collect suitable channels and assign each a weight. */
|
|
for (size_t i = 0; i < tal_count(candidates); i++) {
|
|
struct amount_msat excess, capacity;
|
|
struct amount_sat cumulative_reserve;
|
|
double excess_frac;
|
|
|
|
/* Does the peer have sufficient balance to pay us,
|
|
* even after having taken into account their reserve? */
|
|
if (!amount_msat_sub(&excess, candidates[i].capacity,
|
|
amount_needed))
|
|
continue;
|
|
|
|
/* Channel balance as seen by our node:
|
|
|
|
|<----------------- capacity ----------------->|
|
|
. .
|
|
. |<------------------ their_msat -------------------->|
|
|
. | . |
|
|
. |<----- capacity_to_pay_us ----->|<- their_reserve ->|
|
|
. | | |
|
|
. |<- amount_needed --><- excess ->| |
|
|
. | | |
|
|
|-------|-------------|--------------------------------|-------------------|
|
|
0 ^ ^ ^ funding
|
|
our_reserve our_msat */
|
|
|
|
/* Find capacity and calculate its excess fraction */
|
|
if (!amount_sat_add(&cumulative_reserve,
|
|
candidates[i].c->our_config.channel_reserve,
|
|
candidates[i].c->channel_info.their_config.channel_reserve)
|
|
|| !amount_sat_to_msat(&capacity, candidates[i].c->funding)
|
|
|| !amount_msat_sub_sat(&capacity, capacity, cumulative_reserve)) {
|
|
log_broken(ld->log, "Channel %s capacity overflow!",
|
|
type_to_string(tmpctx, struct short_channel_id, candidates[i].c->scid));
|
|
continue;
|
|
}
|
|
|
|
/* We don't want a 0 probability if 0 excess; it might be the
|
|
* only one! So bump it by 1 msat */
|
|
if (!amount_msat_add(&excess, excess, AMOUNT_MSAT(1))) {
|
|
log_broken(ld->log, "Channel %s excess overflow!",
|
|
type_to_string(tmpctx,
|
|
struct short_channel_id,
|
|
candidates[i].c->scid));
|
|
continue;
|
|
}
|
|
excess_frac = amount_msat_ratio(excess, capacity);
|
|
|
|
if (random_select(excess_frac, &total_weight)) {
|
|
tal_free(r);
|
|
r = tal_arr(ctx, struct route_info *, 1);
|
|
r[0] = tal_dup(r, struct route_info, candidates[i].r);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int cmp_rr_number(const struct routehint_candidate *a,
|
|
const struct routehint_candidate *b,
|
|
void *unused)
|
|
{
|
|
/* They're unique, so can't be equal */
|
|
if (a->c->rr_number > b->c->rr_number)
|
|
return 1;
|
|
assert(a->c->rr_number < b->c->rr_number);
|
|
return -1;
|
|
}
|
|
|
|
/** select_inchan_mpp
|
|
*
|
|
* @brief fallback in case select_inchan cannot find a *single*
|
|
* channel capable of accepting the payment as a whole.
|
|
* Also the main routehint-selector if we are completely unpublished
|
|
* (i.e. all our channels are unpublished), since if we are completely
|
|
* unpublished then the payer cannot fall back to just directly routing
|
|
* to us.
|
|
*/
|
|
static struct route_info **select_inchan_mpp(const tal_t *ctx,
|
|
struct lightningd *ld,
|
|
struct amount_msat amount_needed,
|
|
struct routehint_candidate
|
|
*candidates,
|
|
bool *warning_mpp_capacity)
|
|
{
|
|
/* The total amount we have gathered for incoming channels. */
|
|
struct amount_msat gathered;
|
|
/* Routehint array. */
|
|
struct route_info **routehints;
|
|
|
|
gathered = AMOUNT_MSAT(0);
|
|
routehints = tal_arr(ctx, struct route_info *, 0);
|
|
|
|
/* Sort by rr_number, so we get fresh channels. */
|
|
asort(candidates, tal_count(candidates), cmp_rr_number, NULL);
|
|
for (size_t i = 0; i < tal_count(candidates); i++) {
|
|
if (amount_msat_greater_eq(gathered, amount_needed))
|
|
break;
|
|
|
|
/* Add to current routehints set. */
|
|
if (!amount_msat_add(&gathered, gathered, candidates[i].capacity)) {
|
|
log_broken(ld->log,
|
|
"Gathered channel capacity overflow: "
|
|
"%s + %s",
|
|
type_to_string(tmpctx, struct amount_msat, &gathered),
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
&candidates[i].capacity));
|
|
continue;
|
|
}
|
|
tal_arr_expand(&routehints,
|
|
tal_dup(routehints, struct route_info,
|
|
candidates[i].r));
|
|
/* Put to the back of the round-robin list */
|
|
candidates[i].c->rr_number = ld->rr_counter++;
|
|
}
|
|
|
|
/* Check if we gathered enough. */
|
|
*warning_mpp_capacity = amount_msat_less(gathered, amount_needed);
|
|
|
|
return routehints;
|
|
}
|
|
|
|
/* Encapsulating struct while we wait for gossipd to give us incoming channels */
|
|
struct chanhints {
|
|
bool expose_all_private;
|
|
struct short_channel_id *hints;
|
|
};
|
|
|
|
struct invoice_info {
|
|
struct command *cmd;
|
|
struct preimage payment_preimage;
|
|
struct bolt11 *b11;
|
|
struct json_escape *label;
|
|
struct chanhints *chanhints;
|
|
};
|
|
|
|
static void gossipd_incoming_channels_reply(struct subd *gossipd,
|
|
const u8 *msg,
|
|
const int *fs,
|
|
struct invoice_info *info)
|
|
{
|
|
struct json_stream *response;
|
|
struct invoice invoice;
|
|
char *b11enc;
|
|
const struct invoice_details *details;
|
|
struct wallet *wallet = info->cmd->ld->wallet;
|
|
const struct chanhints *chanhints = info->chanhints;
|
|
|
|
struct routehint_candidate *candidates;
|
|
struct amount_msat offline_amt;
|
|
bool warning_mpp = false;
|
|
bool warning_mpp_capacity = false;
|
|
bool deadends;
|
|
bool node_unpublished;
|
|
|
|
candidates = routehint_candidates(tmpctx, info->cmd->ld, msg,
|
|
chanhints ? chanhints->expose_all_private : false,
|
|
chanhints ? chanhints->hints : NULL,
|
|
&node_unpublished,
|
|
&deadends,
|
|
&offline_amt);
|
|
|
|
/* If they told us to use scids and we couldn't, fail. */
|
|
if (tal_count(candidates) == 0
|
|
&& chanhints && tal_count(chanhints->hints) != 0) {
|
|
was_pending(command_fail(info->cmd,
|
|
INVOICE_HINTS_GAVE_NO_ROUTES,
|
|
"None of those hints were suitable local channels"));
|
|
return;
|
|
}
|
|
|
|
if (tal_count(info->b11->routes) == 0) {
|
|
struct amount_msat needed;
|
|
needed = info->b11->msat ? *info->b11->msat : AMOUNT_MSAT(1);
|
|
|
|
/* If we are not completely unpublished, try with reservoir
|
|
* sampling first.
|
|
*
|
|
* Why do we not do this if we are completely unpublished?
|
|
* Because it is possible that multiple invoices will, by
|
|
* chance, select the same channel as routehint.
|
|
* This single channel might not be able to accept all the
|
|
* incoming payments on all the invoices generated.
|
|
* If we were published, that is fine because the payer can
|
|
* fall back to just attempting to route directly.
|
|
* But if we were unpublished, the only way for the payer to
|
|
* reach us would be via the routehints we provide, so we
|
|
* should make an effort to avoid overlapping incoming
|
|
* channels, which is done by select_inchan_mpp.
|
|
*/
|
|
if (!node_unpublished)
|
|
info->b11->routes = select_inchan(info->b11,
|
|
info->cmd->ld,
|
|
needed,
|
|
candidates);
|
|
/* If we are completely unpublished, or if the above reservoir
|
|
* sampling fails, select channels by round-robin. */
|
|
if (tal_count(info->b11->routes) == 0) {
|
|
info->b11->routes = select_inchan_mpp(info->b11,
|
|
info->cmd->ld,
|
|
needed,
|
|
candidates,
|
|
&warning_mpp_capacity);
|
|
warning_mpp = (tal_count(info->b11->routes) > 1);
|
|
}
|
|
}
|
|
|
|
b11enc = bolt11_encode(info, info->b11, false,
|
|
hsm_sign_b11, info->cmd->ld);
|
|
|
|
/* Check duplicate preimage (unlikely unless they specified it!) */
|
|
if (wallet_invoice_find_by_rhash(wallet,
|
|
&invoice, &info->b11->payment_hash)) {
|
|
was_pending(command_fail(info->cmd,
|
|
INVOICE_PREIMAGE_ALREADY_EXISTS,
|
|
"preimage already used"));
|
|
return;
|
|
}
|
|
|
|
if (!wallet_invoice_create(wallet,
|
|
&invoice,
|
|
info->b11->msat,
|
|
info->label,
|
|
info->b11->expiry,
|
|
b11enc,
|
|
info->b11->description,
|
|
info->b11->features,
|
|
&info->payment_preimage,
|
|
&info->b11->payment_hash,
|
|
NULL)) {
|
|
was_pending(command_fail(info->cmd, INVOICE_LABEL_ALREADY_EXISTS,
|
|
"Duplicate label '%s'",
|
|
info->label->s));
|
|
return;
|
|
}
|
|
|
|
/* Get details */
|
|
details = wallet_invoice_details(info, wallet, invoice);
|
|
|
|
response = json_stream_success(info->cmd);
|
|
json_add_sha256(response, "payment_hash", &details->rhash);
|
|
json_add_u64(response, "expires_at", details->expiry_time);
|
|
json_add_string(response, "bolt11", details->invstring);
|
|
|
|
notify_invoice_creation(info->cmd->ld, info->b11->msat,
|
|
info->payment_preimage, info->label);
|
|
|
|
/* Warn if there's not sufficient incoming capacity. */
|
|
if (tal_count(info->b11->routes) == 0) {
|
|
log_unusual(info->cmd->ld->log,
|
|
"invoice: insufficient incoming capacity for %s%s",
|
|
info->b11->msat
|
|
? type_to_string(tmpctx, struct amount_msat,
|
|
info->b11->msat)
|
|
: "0",
|
|
amount_msat_greater(offline_amt, AMOUNT_MSAT(0))
|
|
? " (among currently connected peers)" : "");
|
|
|
|
if (amount_msat_greater(offline_amt, AMOUNT_MSAT(0))) {
|
|
json_add_string(response, "warning_offline",
|
|
"No channel with a peer that is currently connected"
|
|
" has sufficient incoming capacity");
|
|
} else if (deadends) {
|
|
json_add_string(response, "warning_deadends",
|
|
"No channel with a peer that is not a dead end");
|
|
} else if (tal_count(candidates) == 0) {
|
|
json_add_string(response, "warning_capacity",
|
|
"No channels");
|
|
} else {
|
|
json_add_string(response, "warning_capacity",
|
|
"No channel with a peer that has sufficient incoming capacity");
|
|
}
|
|
}
|
|
|
|
if (warning_mpp)
|
|
json_add_string(response, "warning_mpp",
|
|
"The invoice might only be payable by MPP-capable payers.");
|
|
if (warning_mpp_capacity)
|
|
json_add_string(response, "warning_mpp_capacity",
|
|
"The total incoming capacity is still insufficient even if the payer had MPP capability.");
|
|
|
|
was_pending(command_success(info->cmd, response));
|
|
}
|
|
|
|
#if DEVELOPER
|
|
/* Since this is a dev-only option, we will crash if dev-routes is not
|
|
* an array-of-arrays-of-correct-items. */
|
|
static struct route_info *unpack_route(const tal_t *ctx,
|
|
const char *buffer,
|
|
const jsmntok_t *routetok)
|
|
{
|
|
const jsmntok_t *t;
|
|
size_t i;
|
|
struct route_info *route = tal_arr(ctx, struct route_info, routetok->size);
|
|
|
|
json_for_each_arr(i, t, routetok) {
|
|
const jsmntok_t *pubkey, *fee_base, *fee_prop, *scid, *cltv;
|
|
struct route_info *r = &route[i];
|
|
u32 cltv_u32;
|
|
|
|
pubkey = json_get_member(buffer, t, "id");
|
|
scid = json_get_member(buffer, t, "short_channel_id");
|
|
fee_base = json_get_member(buffer, t, "fee_base_msat");
|
|
fee_prop = json_get_member(buffer, t,
|
|
"fee_proportional_millionths");
|
|
cltv = json_get_member(buffer, t, "cltv_expiry_delta");
|
|
|
|
if (!json_to_node_id(buffer, pubkey, &r->pubkey)
|
|
|| !json_to_short_channel_id(buffer, scid,
|
|
&r->short_channel_id)
|
|
|| !json_to_number(buffer, fee_base, &r->fee_base_msat)
|
|
|| !json_to_number(buffer, fee_prop,
|
|
&r->fee_proportional_millionths)
|
|
|| !json_to_number(buffer, cltv, &cltv_u32))
|
|
abort();
|
|
/* We don't have a json_to_u16 */
|
|
r->cltv_expiry_delta = cltv_u32;
|
|
}
|
|
return route;
|
|
}
|
|
|
|
static struct route_info **unpack_routes(const tal_t *ctx,
|
|
const char *buffer,
|
|
const jsmntok_t *routestok)
|
|
{
|
|
struct route_info **routes;
|
|
const jsmntok_t *t;
|
|
size_t i;
|
|
|
|
if (!routestok)
|
|
return NULL;
|
|
|
|
routes = tal_arr(ctx, struct route_info *, routestok->size);
|
|
json_for_each_arr(i, t, routestok)
|
|
routes[i] = unpack_route(routes, buffer, t);
|
|
|
|
return routes;
|
|
}
|
|
#endif /* DEVELOPER */
|
|
|
|
static struct command_result *param_positive_msat_or_any(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
struct amount_msat **msat)
|
|
{
|
|
if (json_tok_streq(buffer, tok, "any")) {
|
|
*msat = NULL;
|
|
return NULL;
|
|
}
|
|
*msat = tal(cmd, struct amount_msat);
|
|
if (parse_amount_msat(*msat, buffer + tok->start, tok->end - tok->start)
|
|
&& !amount_msat_eq(**msat, AMOUNT_MSAT(0)))
|
|
return NULL;
|
|
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"should be positive msat or 'any'");
|
|
}
|
|
|
|
/* Parse time with optional suffix, return seconds */
|
|
static struct command_result *param_time(struct command *cmd, const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
uint64_t **secs)
|
|
{
|
|
/* We need to manipulate this, so make copy */
|
|
jsmntok_t timetok = *tok;
|
|
u64 mul;
|
|
char s;
|
|
struct {
|
|
char suffix;
|
|
u64 mul;
|
|
} suffixes[] = {
|
|
{ 's', 1 },
|
|
{ 'm', 60 },
|
|
{ 'h', 60*60 },
|
|
{ 'd', 24*60*60 },
|
|
{ 'w', 7*24*60*60 } };
|
|
|
|
mul = 1;
|
|
if (timetok.end == timetok.start)
|
|
s = '\0';
|
|
else
|
|
s = buffer[timetok.end - 1];
|
|
for (size_t i = 0; i < ARRAY_SIZE(suffixes); i++) {
|
|
if (s == suffixes[i].suffix) {
|
|
mul = suffixes[i].mul;
|
|
timetok.end--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*secs = tal(cmd, uint64_t);
|
|
if (json_to_u64(buffer, &timetok, *secs)) {
|
|
if (mul_overflows_u64(**secs, mul)) {
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"value too large");
|
|
}
|
|
**secs *= mul;
|
|
return NULL;
|
|
}
|
|
|
|
return command_fail_badparam(cmd, name, buffer, tok,
|
|
"should be a number with optional {s,m,h,d,w} suffix");
|
|
}
|
|
|
|
static struct command_result *param_chanhints(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
struct chanhints **chanhints)
|
|
{
|
|
bool boolhint;
|
|
|
|
*chanhints = tal(cmd, struct chanhints);
|
|
|
|
/* Could be simply "true" or "false" */
|
|
if (json_to_bool(buffer, tok, &boolhint)) {
|
|
(*chanhints)->expose_all_private = boolhint;
|
|
(*chanhints)->hints
|
|
= tal_arr(*chanhints, struct short_channel_id, 0);
|
|
return NULL;
|
|
}
|
|
|
|
(*chanhints)->expose_all_private = false;
|
|
/* Could be a single short_channel_id or an array */
|
|
if (tok->type == JSMN_ARRAY) {
|
|
size_t i;
|
|
const jsmntok_t *t;
|
|
|
|
(*chanhints)->hints
|
|
= tal_arr(*chanhints, struct short_channel_id,
|
|
tok->size);
|
|
json_for_each_arr(i, t, tok) {
|
|
if (!json_to_short_channel_id(buffer, t,
|
|
&(*chanhints)->hints[i])) {
|
|
return command_fail_badparam(cmd, name, buffer, t,
|
|
"should be a short channel id");
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Otherwise should be a short_channel_id */
|
|
return param_short_channel_id(cmd, name, buffer, tok,
|
|
&(*chanhints)->hints);
|
|
}
|
|
|
|
static struct command_result *param_preimage(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
struct preimage **preimage)
|
|
{
|
|
*preimage = tal(cmd, struct preimage);
|
|
if (!hex_decode(buffer + tok->start, tok->end - tok->start,
|
|
*preimage, sizeof(**preimage)))
|
|
return command_fail_badparam(cmd, "preimage",
|
|
buffer, tok,
|
|
"should be 64 hex digits");
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *json_invoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
const jsmntok_t *fallbacks;
|
|
struct amount_msat *msatoshi_val;
|
|
struct invoice_info *info;
|
|
const char *desc_val;
|
|
const u8 **fallback_scripts = NULL;
|
|
u64 *expiry;
|
|
struct sha256 rhash;
|
|
struct secret payment_secret;
|
|
struct preimage *preimage;
|
|
u32 *cltv;
|
|
#if DEVELOPER
|
|
const jsmntok_t *routes;
|
|
#endif
|
|
|
|
info = tal(cmd, struct invoice_info);
|
|
info->cmd = cmd;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("msatoshi", param_positive_msat_or_any, &msatoshi_val),
|
|
p_req("label", param_label, &info->label),
|
|
p_req("description", param_escaped_string, &desc_val),
|
|
p_opt_def("expiry", param_time, &expiry, 3600*24*7),
|
|
p_opt("fallbacks", param_array, &fallbacks),
|
|
p_opt("preimage", param_preimage, &preimage),
|
|
p_opt("exposeprivatechannels", param_chanhints,
|
|
&info->chanhints),
|
|
p_opt_def("cltv", param_number, &cltv,
|
|
cmd->ld->config.cltv_final),
|
|
#if DEVELOPER
|
|
p_opt("dev-routes", param_array, &routes),
|
|
#endif
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (strlen(info->label->s) > INVOICE_MAX_LABEL_LEN) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Label '%s' over %u bytes", info->label->s,
|
|
INVOICE_MAX_LABEL_LEN);
|
|
}
|
|
|
|
if (strlen(desc_val) >= BOLT11_FIELD_BYTE_LIMIT) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Descriptions greater than %d bytes "
|
|
"not yet supported "
|
|
"(description length %zu)",
|
|
BOLT11_FIELD_BYTE_LIMIT,
|
|
strlen(desc_val));
|
|
}
|
|
|
|
if (msatoshi_val
|
|
&& amount_msat_greater(*msatoshi_val, chainparams->max_payment)) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"msatoshi cannot exceed %s",
|
|
type_to_string(tmpctx, struct amount_msat,
|
|
&chainparams->max_payment));
|
|
}
|
|
|
|
if (fallbacks) {
|
|
size_t i;
|
|
const jsmntok_t *t;
|
|
|
|
fallback_scripts = tal_arr(cmd, const u8 *, fallbacks->size);
|
|
json_for_each_arr(i, t, fallbacks) {
|
|
struct command_result *r;
|
|
|
|
r = parse_fallback(cmd, buffer, t, &fallback_scripts[i]);
|
|
if (r)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (preimage)
|
|
info->payment_preimage = *preimage;
|
|
else
|
|
/* Generate random secret preimage. */
|
|
randombytes_buf(&info->payment_preimage,
|
|
sizeof(info->payment_preimage));
|
|
/* Generate preimage hash. */
|
|
sha256(&rhash, &info->payment_preimage, sizeof(info->payment_preimage));
|
|
/* Generate payment secret. */
|
|
invoice_secret(&info->payment_preimage, &payment_secret);
|
|
|
|
info->b11 = new_bolt11(info, msatoshi_val);
|
|
info->b11->chain = chainparams;
|
|
info->b11->timestamp = time_now().ts.tv_sec;
|
|
info->b11->payment_hash = rhash;
|
|
info->b11->receiver_id = cmd->ld->id;
|
|
info->b11->min_final_cltv_expiry = *cltv;
|
|
info->b11->expiry = *expiry;
|
|
info->b11->description = tal_steal(info->b11, desc_val);
|
|
info->b11->description_hash = NULL;
|
|
info->b11->payment_secret = tal_dup(info->b11, struct secret,
|
|
&payment_secret);
|
|
info->b11->features = tal_dup_talarr(info->b11, u8,
|
|
cmd->ld->our_features
|
|
->bits[BOLT11_FEATURE]);
|
|
|
|
#if DEVELOPER
|
|
info->b11->routes = unpack_routes(info->b11, buffer, routes);
|
|
#else
|
|
info->b11->routes = NULL;
|
|
#endif
|
|
if (fallback_scripts)
|
|
info->b11->fallbacks = tal_steal(info->b11, fallback_scripts);
|
|
|
|
subd_req(cmd, cmd->ld->gossip,
|
|
take(towire_gossipd_get_incoming_channels(NULL)),
|
|
-1, 0, gossipd_incoming_channels_reply, info);
|
|
|
|
return command_still_pending(cmd);
|
|
}
|
|
|
|
static const struct json_command invoice_command = {
|
|
"invoice",
|
|
"payment",
|
|
json_invoice,
|
|
"Create an invoice for {msatoshi} with {label} "
|
|
"and {description} with optional {expiry} seconds "
|
|
"(default 1 week), optional {fallbacks} address list"
|
|
"(default empty list) and optional {preimage} "
|
|
"(default autogenerated)"};
|
|
AUTODATA(json_command, &invoice_command);
|
|
|
|
static void json_add_invoices(struct json_stream *response,
|
|
struct wallet *wallet,
|
|
const struct json_escape *label,
|
|
const struct sha256 *payment_hash)
|
|
{
|
|
struct invoice_iterator it;
|
|
const struct invoice_details *details;
|
|
struct invoice invoice;
|
|
|
|
/* Don't iterate entire db if we're just after one. */
|
|
if (label) {
|
|
if (wallet_invoice_find_by_label(wallet, &invoice, label)) {
|
|
details =
|
|
wallet_invoice_details(response, wallet, invoice);
|
|
json_object_start(response, NULL);
|
|
json_add_invoice(response, details);
|
|
json_object_end(response);
|
|
}
|
|
} else if (payment_hash != NULL) {
|
|
if (wallet_invoice_find_by_rhash(wallet, &invoice,
|
|
payment_hash)) {
|
|
json_object_start(response, NULL);
|
|
json_add_invoice(
|
|
response,
|
|
wallet_invoice_details(response, wallet, invoice));
|
|
json_object_end(response);
|
|
}
|
|
|
|
} else {
|
|
memset(&it, 0, sizeof(it));
|
|
while (wallet_invoice_iterate(wallet, &it)) {
|
|
details = wallet_invoice_iterator_deref(response,
|
|
wallet, &it);
|
|
json_object_start(response, NULL);
|
|
json_add_invoice(response, details);
|
|
json_object_end(response);
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct command_result *json_listinvoices(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct json_escape *label;
|
|
struct json_stream *response;
|
|
struct wallet *wallet = cmd->ld->wallet;
|
|
const char *invstring;
|
|
struct sha256 *payment_hash;
|
|
char *fail;
|
|
struct bolt11 *b11;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_opt("label", param_label, &label),
|
|
p_opt("invstring", param_string, &invstring),
|
|
p_opt("payment_hash", param_sha256, &payment_hash),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if ((label && invstring) || (label && payment_hash) ||
|
|
(invstring && payment_hash)) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Can only specify one of"
|
|
" {label}, {invstring} or {payment_hash}");
|
|
}
|
|
|
|
/* Extract the payment_hash from the invoice. */
|
|
if (invstring != NULL) {
|
|
b11 = bolt11_decode(cmd, invstring, cmd->ld->our_features, NULL,
|
|
NULL, &fail);
|
|
payment_hash = &b11->payment_hash;
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
json_array_start(response, "invoices");
|
|
json_add_invoices(response, wallet, label, payment_hash);
|
|
json_array_end(response);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command listinvoices_command = {
|
|
"listinvoices",
|
|
"payment",
|
|
json_listinvoices,
|
|
"Show invoice matching {label}, {invstring} or {payment_hash} (or all, if "
|
|
"no query parameter specified)"
|
|
};
|
|
AUTODATA(json_command, &listinvoices_command);
|
|
|
|
static struct command_result *json_delinvoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct invoice i;
|
|
const struct invoice_details *details;
|
|
struct json_stream *response;
|
|
const char *status, *actual_status;
|
|
struct json_escape *label;
|
|
struct wallet *wallet = cmd->ld->wallet;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("label", param_label, &label),
|
|
p_req("status", param_string, &status),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (!wallet_invoice_find_by_label(wallet, &i, label)) {
|
|
return command_fail(cmd, INVOICE_NOT_FOUND, "Unknown invoice");
|
|
}
|
|
|
|
details = wallet_invoice_details(cmd, cmd->ld->wallet, i);
|
|
|
|
/* This is time-sensitive, so only call once; otherwise error msg
|
|
* might not make sense if it changed! */
|
|
actual_status = invoice_status_str(details);
|
|
if (!streq(actual_status, status)) {
|
|
struct json_stream *js;
|
|
js = json_stream_fail(cmd, INVOICE_STATUS_UNEXPECTED,
|
|
tal_fmt(tmpctx,
|
|
"Invoice status is %s not %s",
|
|
actual_status, status));
|
|
json_add_string(js, "current_status", actual_status);
|
|
json_add_string(js, "expected_status", status);
|
|
json_object_end(js);
|
|
return command_failed(cmd, js);
|
|
}
|
|
|
|
if (!wallet_invoice_delete(wallet, i)) {
|
|
log_broken(cmd->ld->log,
|
|
"Error attempting to remove invoice %"PRIu64,
|
|
i.id);
|
|
/* FIXME: allocate a generic DATABASE_ERROR code. */
|
|
return command_fail(cmd, LIGHTNINGD, "Database error");
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
json_add_invoice(response, details);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command delinvoice_command = {
|
|
"delinvoice",
|
|
"payment",
|
|
json_delinvoice,
|
|
"Delete unpaid invoice {label} with {status}",
|
|
};
|
|
AUTODATA(json_command, &delinvoice_command);
|
|
|
|
static struct command_result *json_delexpiredinvoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
u64 *maxexpirytime;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_opt_def("maxexpirytime", param_u64, &maxexpirytime,
|
|
time_now().ts.tv_sec),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
wallet_invoice_delete_expired(cmd->ld->wallet, *maxexpirytime);
|
|
|
|
return command_success(cmd, json_stream_success(cmd));
|
|
}
|
|
static const struct json_command delexpiredinvoice_command = {
|
|
"delexpiredinvoice",
|
|
"payment",
|
|
json_delexpiredinvoice,
|
|
"Delete all expired invoices that expired as of given {maxexpirytime} (a UNIX epoch time), or all expired invoices if not specified"
|
|
};
|
|
AUTODATA(json_command, &delexpiredinvoice_command);
|
|
|
|
static struct command_result *json_waitanyinvoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
u64 *pay_index;
|
|
u64 *timeout;
|
|
struct wallet *wallet = cmd->ld->wallet;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_opt_def("lastpay_index", param_u64, &pay_index, 0),
|
|
p_opt("timeout", ¶m_u64, &timeout),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
/*~ We allocate the timeout and the wallet-waitanyinvoice
|
|
* in the cmd context, so whichever one manages to complete
|
|
* the command first (and destroy the cmd context)
|
|
* auto-cancels the other, is not tal amazing?
|
|
*/
|
|
if (timeout)
|
|
(void) new_reltimer(cmd->ld->timers, cmd,
|
|
time_from_sec(*timeout),
|
|
&wait_timed_out, cmd);
|
|
|
|
/* Set command as pending. We do not know if
|
|
* wallet_invoice_waitany will return immediately
|
|
* or not, so indicating pending is safest. */
|
|
fixme_ignore(command_still_pending(cmd));
|
|
|
|
/* Find next paid invoice. */
|
|
wallet_invoice_waitany(cmd, wallet, *pay_index,
|
|
&wait_on_invoice, (void*) cmd);
|
|
|
|
return command_its_complicated("wallet_invoice_waitany might complete"
|
|
" immediately, but we also call it as a"
|
|
" callback so plumbing through the return"
|
|
" is non-trivial.");
|
|
}
|
|
|
|
|
|
static const struct json_command waitanyinvoice_command = {
|
|
"waitanyinvoice",
|
|
"payment",
|
|
json_waitanyinvoice,
|
|
"Wait for the next invoice to be paid, after {lastpay_index} (if supplied). "
|
|
"If {timeout} seconds is reached while waiting, fail with an error."
|
|
};
|
|
AUTODATA(json_command, &waitanyinvoice_command);
|
|
|
|
/* Wait for an incoming payment matching the `label` in the JSON
|
|
* command. This will either return immediately if the payment has
|
|
* already been received or it may add the `cmd` to the list of
|
|
* waiters, if the payment is still pending.
|
|
*/
|
|
static struct command_result *json_waitinvoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct invoice i;
|
|
const struct invoice_details *details;
|
|
struct wallet *wallet = cmd->ld->wallet;
|
|
struct json_escape *label;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("label", param_label, &label),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (!wallet_invoice_find_by_label(wallet, &i, label)) {
|
|
return command_fail(cmd, LIGHTNINGD, "Label not found");
|
|
}
|
|
details = wallet_invoice_details(cmd, cmd->ld->wallet, i);
|
|
|
|
/* If paid or expired return immediately */
|
|
if (details->state == PAID || details->state == EXPIRED) {
|
|
return tell_waiter(cmd, &i);
|
|
} else {
|
|
/* There is an unpaid one matching, let's wait... */
|
|
fixme_ignore(command_still_pending(cmd));
|
|
wallet_invoice_waitone(cmd, wallet, i,
|
|
&wait_on_invoice, (void *) cmd);
|
|
return command_its_complicated("wallet_invoice_waitone might"
|
|
" complete immediately");
|
|
}
|
|
}
|
|
|
|
static const struct json_command waitinvoice_command = {
|
|
"waitinvoice",
|
|
"payment",
|
|
json_waitinvoice,
|
|
"Wait for an incoming payment matching the invoice with {label}, or if the invoice expires"
|
|
};
|
|
AUTODATA(json_command, &waitinvoice_command);
|
|
|
|
static struct command_result *json_decodepay(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct bolt11 *b11;
|
|
struct json_stream *response;
|
|
const char *str, *desc;
|
|
char *fail;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("bolt11", param_string, &str),
|
|
p_opt("description", param_string, &desc),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
b11 = bolt11_decode(cmd, str, cmd->ld->our_features, desc, NULL,
|
|
&fail);
|
|
|
|
if (!b11) {
|
|
return command_fail(cmd, LIGHTNINGD, "Invalid bolt11: %s", fail);
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
json_add_bolt11(response, b11);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command decodepay_command = {
|
|
"decodepay",
|
|
"payment",
|
|
json_decodepay,
|
|
"Decode {bolt11}, using {description} if necessary"
|
|
};
|
|
AUTODATA(json_command, &decodepay_command);
|
|
|
|
/* If we fail because it exists, we also return the clashing invoice */
|
|
static struct command_result *fail_exists(struct command *cmd,
|
|
const struct json_escape *label)
|
|
{
|
|
struct json_stream *data;
|
|
struct invoice invoice;
|
|
struct wallet *wallet = cmd->ld->wallet;
|
|
|
|
data = json_stream_fail(cmd, INVOICE_LABEL_ALREADY_EXISTS,
|
|
"Duplicate label");
|
|
if (!wallet_invoice_find_by_label(wallet, &invoice, label))
|
|
fatal("Duplicate invoice %s not found any more?",
|
|
label->s);
|
|
|
|
json_add_invoice(data, wallet_invoice_details(cmd, wallet, invoice));
|
|
json_object_end(data);
|
|
|
|
return command_failed(cmd, data);
|
|
}
|
|
|
|
static struct command_result *json_createinvoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
const char *invstring;
|
|
struct json_escape *label;
|
|
struct preimage *preimage;
|
|
struct invoice invoice;
|
|
struct sha256 payment_hash;
|
|
struct json_stream *response;
|
|
struct bolt11 *b11;
|
|
struct sha256 hash;
|
|
u5 *sig;
|
|
bool have_n;
|
|
char *fail;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("invstring", param_string, &invstring),
|
|
p_req("label", param_label, &label),
|
|
p_req("preimage", param_preimage, &preimage),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
sha256(&payment_hash, preimage, sizeof(*preimage));
|
|
b11 = bolt11_decode_nosig(cmd, invstring, cmd->ld->our_features,
|
|
NULL, chainparams, &hash, &sig, &have_n,
|
|
&fail);
|
|
if (b11) {
|
|
/* This adds the signature */
|
|
char *b11enc = bolt11_encode(cmd, b11, have_n,
|
|
hsm_sign_b11, cmd->ld);
|
|
|
|
if (!b11->description)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Missing description in invoice");
|
|
|
|
if (!b11->expiry)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Missing expiry in invoice");
|
|
|
|
if (!sha256_eq(&payment_hash, &b11->payment_hash))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Incorrect preimage");
|
|
|
|
if (!wallet_invoice_create(cmd->ld->wallet,
|
|
&invoice,
|
|
b11->msat,
|
|
label,
|
|
b11->expiry,
|
|
b11enc,
|
|
b11->description,
|
|
b11->features,
|
|
preimage,
|
|
&payment_hash,
|
|
NULL))
|
|
return fail_exists(cmd, label);
|
|
|
|
notify_invoice_creation(cmd->ld, b11->msat, *preimage, label);
|
|
} else {
|
|
#if EXPERIMENTAL_FEATURES
|
|
struct tlv_invoice *inv;
|
|
struct sha256 *local_offer_id;
|
|
|
|
inv = invoice_decode_nosig(cmd, invstring, strlen(invstring),
|
|
cmd->ld->our_features, chainparams,
|
|
&fail);
|
|
if (inv) {
|
|
char *b12enc;
|
|
struct amount_msat msat;
|
|
const char *desc;
|
|
u32 expiry;
|
|
enum offer_status status;
|
|
|
|
if (inv->signature)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"invoice already signed");
|
|
hsm_sign_b12_invoice(cmd->ld, inv);
|
|
b12enc = invoice_encode(cmd, inv);
|
|
|
|
if (inv->offer_id
|
|
&& wallet_offer_find(tmpctx, cmd->ld->wallet,
|
|
inv->offer_id, NULL, &status)) {
|
|
if (!offer_status_active(status))
|
|
return command_fail(cmd, INVOICE_OFFER_INACTIVE,
|
|
"offer not active");
|
|
local_offer_id = inv->offer_id;
|
|
} else
|
|
local_offer_id = NULL;
|
|
|
|
if (inv->amount)
|
|
msat = amount_msat(*inv->amount);
|
|
|
|
if (inv->relative_expiry)
|
|
expiry = *inv->relative_expiry;
|
|
else
|
|
expiry = BOLT12_DEFAULT_REL_EXPIRY;
|
|
|
|
if (!inv->payment_hash)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Missing payment_hash in invoice");
|
|
if (!sha256_eq(&payment_hash, inv->payment_hash))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Incorrect preimage");
|
|
|
|
if (!inv->description)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Missing description in invoice");
|
|
desc = tal_strndup(cmd,
|
|
cast_signed(char *, inv->description),
|
|
tal_bytelen(inv->description));
|
|
|
|
if (!wallet_invoice_create(cmd->ld->wallet,
|
|
&invoice,
|
|
inv->amount ? &msat : NULL,
|
|
label,
|
|
expiry,
|
|
b12enc,
|
|
desc,
|
|
inv->features,
|
|
preimage,
|
|
&payment_hash,
|
|
local_offer_id))
|
|
return fail_exists(cmd, label);
|
|
|
|
notify_invoice_creation(cmd->ld,
|
|
inv->amount ? &msat : NULL,
|
|
*preimage, label);
|
|
} else
|
|
#endif /* EXPERIMENTAL_FEATURES */
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unparsable invoice '%s': %s",
|
|
invstring, fail);
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
json_add_invoice(response,
|
|
wallet_invoice_details(cmd, cmd->ld->wallet, invoice));
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command createinvoice_command = {
|
|
"createinvoice",
|
|
"payment",
|
|
json_createinvoice,
|
|
"Lowlevel command to sign and create invoice {invstring}, resolved with {preimage}, using unique {label}."
|
|
};
|
|
|
|
AUTODATA(json_command, &createinvoice_command);
|
|
|