core-lightning/plugins/offers_invreq_hook.c
Rusty Russell af46a4f57d fetchinvoice: allow amounts to be specified.
As per lastest revision of the spec, we can specify amounts in invoice
requests even if the offer already specifies it, as long as we exceed
the amount given.  This allows for tipping, and amount obfuscation.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2021-01-09 12:45:31 +01:00

868 lines
25 KiB
C

#include <bitcoin/chainparams.h>
#include <bitcoin/preimage.h>
#include <ccan/cast/cast.h>
#include <common/bech32_util.h>
#include <common/bolt12.h>
#include <common/bolt12_merkle.h>
#include <common/iso4217.h>
#include <common/json_stream.h>
#include <common/overflows.h>
#include <common/type_to_string.h>
#include <plugins/offers.h>
#include <plugins/offers_invreq_hook.h>
#include <secp256k1_schnorrsig.h>
/* We need to keep the reply path around so we can reply with invoice */
struct invreq {
struct tlv_invoice_request *invreq;
const char *buf;
const jsmntok_t *replytok;
/* The offer, once we've looked it up. */
struct tlv_offer *offer;
/* The invoice we're preparing (can require additional lookups) */
struct tlv_invoice *inv;
/* The preimage for the invoice. */
struct preimage preimage;
};
static struct command_result *WARN_UNUSED_RESULT
fail_invreq_level(struct command *cmd,
const struct invreq *invreq,
enum log_level l,
const char *fmt, va_list ap)
{
char *full_fmt, *msg;
struct tlv_invoice_error *err;
u8 *errdata;
full_fmt = tal_fmt(tmpctx, "Failed invoice_request %s",
invrequest_encode(tmpctx, invreq->invreq));
if (invreq->invreq->offer_id)
tal_append_fmt(&full_fmt, " for offer %s",
type_to_string(tmpctx, struct sha256,
invreq->invreq->offer_id));
tal_append_fmt(&full_fmt, ": %s", fmt);
msg = tal_vfmt(tmpctx, full_fmt, ap);
plugin_log(cmd->plugin, l, "%s", msg);
/* Don't send back internal error details. */
if (l == LOG_BROKEN)
msg = "Internal error";
err = tlv_invoice_error_new(cmd);
/* Remove NUL terminator */
err->error = tal_dup_arr(err, char, msg, strlen(msg), 0);
/* FIXME: Add suggested_value / erroneous_field! */
errdata = tal_arr(cmd, u8, 0);
towire_invoice_error(&errdata, err);
return send_onion_reply(cmd, invreq->buf, invreq->replytok,
"invoice_error", errdata);
}
static struct command_result *WARN_UNUSED_RESULT
fail_invreq(struct command *cmd,
const struct invreq *invreq,
const char *fmt, ...)
{
va_list ap;
struct command_result *ret;
va_start(ap, fmt);
ret = fail_invreq_level(cmd, invreq, LOG_DBG, fmt, ap);
va_end(ap);
return ret;
}
static struct command_result *WARN_UNUSED_RESULT
fail_internalerr(struct command *cmd,
const struct invreq *invreq,
const char *fmt, ...)
{
va_list ap;
struct command_result *ret;
va_start(ap, fmt);
ret = fail_invreq_level(cmd, invreq, LOG_BROKEN, fmt, ap);
va_end(ap);
return ret;
}
#define invreq_must_have(cmd_, ir_, fld_) \
test_field(cmd_, ir_, ir_->invreq->fld_ != NULL, #fld_, "missing")
#define invreq_must_not_have(cmd_, ir_, fld_) \
test_field(cmd_, ir_, ir_->invreq->fld_ == NULL, #fld_, "unexpected")
static struct command_result *
test_field(struct command *cmd,
const struct invreq *invreq,
bool test, const char *fieldname, const char *what)
{
if (!test)
return fail_invreq(cmd, invreq, "%s %s", what, fieldname);
return NULL;
}
/* BOLT-offers #12:
* - if the invoice corresponds to an offer with `recurrence`:
* ...
* - if it sets `relative_expiry`:
* - MUST NOT set `relative_expiry` `seconds_from_timestamp` more than the
* number of seconds after `timestamp` that payment for this period will
* be accepted.
*/
static void set_recurring_inv_expiry(struct tlv_invoice *inv, u64 last_pay)
{
inv->relative_expiry = tal(inv, u32);
/* Don't give them a 0 second invoice, even if it's true. */
if (last_pay <= *inv->timestamp)
*inv->relative_expiry = 1;
else
*inv->relative_expiry = last_pay - *inv->timestamp;
/* FIXME: Shorten expiry if we're doing currency conversion! */
}
/* We rely on label forms for uniqueness. */
static void json_add_label(struct json_stream *js,
const struct sha256 *offer_id,
const struct pubkey32 *payer_key,
const u32 counter)
{
char *label;
label = tal_fmt(tmpctx, "%s-%s-%u",
type_to_string(tmpctx, struct sha256, offer_id),
type_to_string(tmpctx, struct pubkey32,
payer_key),
counter);
json_add_string(js, "label", label);
}
/* Note: this can actually happen if a single-use offer is already
* used at the same time between the check and now.
*/
static struct command_result *error(struct command *cmd,
const char *buf,
const jsmntok_t *err,
struct invreq *ir)
{
return fail_internalerr(cmd, ir,
"Got JSON error: %.*s",
json_tok_full_len(err),
json_tok_full(buf, err));
}
static struct command_result *createinvoice_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
char *hrp;
u8 *rawinv;
const jsmntok_t *t;
/* We have a signed invoice, use it as a reply. */
t = json_get_member(buf, result, "bolt12");
if (!from_bech32_charset(tmpctx, buf + t->start, t->end - t->start,
&hrp, &rawinv)) {
return fail_internalerr(cmd, ir,
"Bad creatinvoice bolt12 string %.*s",
json_tok_full_len(t),
json_tok_full(buf, t));
}
return send_onion_reply(cmd, ir->buf, ir->replytok, "invoice", rawinv);
}
static struct command_result *create_invoicereq(struct command *cmd,
struct invreq *ir)
{
struct out_req *req;
/* Now, write invoice to db (returns the signed version) */
req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice",
createinvoice_done, error, ir);
json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv));
json_add_preimage(req->js, "preimage", &ir->preimage);
json_add_label(req->js, ir->inv->offer_id, ir->inv->payer_key,
ir->inv->recurrence_counter
? *ir->inv->recurrence_counter : 0);
return send_outreq(cmd->plugin, req);
}
static struct command_result *check_period(struct command *cmd,
struct invreq *ir,
u64 basetime)
{
u64 period_idx;
u64 paywindow_start, paywindow_end;
struct command_result *err;
/* If we have a recurrence base, that overrides. */
if (ir->offer->recurrence_base)
basetime = ir->offer->recurrence_base->basetime;
/* BOLT-offers #12:
* - if the invoice corresponds to an offer with `recurrence`:
* - MUST set `recurrence_basetime` to the start of period #0 as
* calculated by [Period Calculation](#offer-period-calculation).
*/
ir->inv->recurrence_basetime = tal_dup(ir->inv, u64, &basetime);
period_idx = *ir->invreq->recurrence_counter;
/* BOLT-offers #12:
* - if the offer had `recurrence_base` and `start_any_period`
* was 1:
* - MUST fail the request if there is no `recurrence_start`
* field.
* - MUST consider the period index for this request to be the
* `recurrence_start` field plus the `recurrence_counter`
* `counter` field.
*/
if (ir->offer->recurrence_base
&& ir->offer->recurrence_base->start_any_period) {
err = invreq_must_have(cmd, ir, recurrence_start);
if (err)
return err;
period_idx += *ir->invreq->recurrence_start;
} else {
/* BOLT-offers #12:
*
* - otherwise:
* - MUST fail the request if there is a `recurrence_start`
* field.
* - MUST consider the period index for this request to be the
* `recurrence_counter` `counter` field.
*/
err = invreq_must_not_have(cmd, ir, recurrence_start);
if (err)
return err;
}
/* BOLT-offers #12:
* - if the offer has a `recurrence_limit`:
* - MUST fail the request if the period index is greater than
* `max_period`.
*/
if (ir->offer->recurrence_limit
&& period_idx > *ir->offer->recurrence_limit) {
return fail_invreq(cmd, ir,
"period_index %"PRIu64" too great",
period_idx);
}
offer_period_paywindow(ir->offer->recurrence,
ir->offer->recurrence_paywindow,
ir->offer->recurrence_base,
basetime, period_idx,
&paywindow_start, &paywindow_end);
if (*ir->inv->timestamp < paywindow_start) {
return fail_invreq(cmd, ir,
"period_index %"PRIu64
" too early (start %"PRIu64")",
period_idx,
paywindow_start);
}
if (*ir->inv->timestamp > paywindow_end) {
return fail_invreq(cmd, ir,
"period_index %"PRIu64
" too late (ended %"PRIu64")",
period_idx,
paywindow_end);
}
set_recurring_inv_expiry(ir->inv, paywindow_end);
/* BOLT-offers #12:
*
* - if `recurrence_counter` is non-zero:
*...
* - if the offer had a `recurrence_paywindow`:
*...
* - if `proportional_amount` is 1:
* - MUST adjust the *base invoice amount* proportional to time
* remaining in the period.
*/
if (*ir->invreq->recurrence_counter != 0
&& ir->offer->recurrence_paywindow
&& ir->offer->recurrence_paywindow->proportional_amount == 1) {
u64 start = offer_period_start(basetime, period_idx,
ir->offer->recurrence);
u64 end = offer_period_start(basetime, period_idx + 1,
ir->offer->recurrence);
if (*ir->inv->timestamp > start) {
*ir->inv->amount
*= (double)((*ir->inv->timestamp - start)
/ (end - start));
/* Round up to make it non-zero if necessary. */
if (*ir->inv->amount == 0)
*ir->inv->amount = 1;
}
}
return create_invoicereq(cmd, ir);
}
static struct command_result *prev_invoice_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
const jsmntok_t *status, *arr, *b12;
struct tlv_invoice *previnv;
char *fail;
/* Was it created? */
arr = json_get_member(buf, result, "invoices");
if (arr->size == 0) {
return fail_invreq(cmd, ir,
"No previous invoice #%u",
*ir->inv->recurrence_counter - 1);
}
/* Was it paid? */
status = json_get_member(buf, arr + 1, "status");
if (!json_tok_streq(buf, status, "paid")) {
return fail_invreq(cmd, ir,
"Previous invoice #%u status *.%s",
*ir->inv->recurrence_counter - 1,
json_tok_full_len(status),
json_tok_full(buf, status));
}
/* Decode it */
b12 = json_get_member(buf, arr + 1, "bolt12");
if (!b12) {
return fail_internalerr(cmd, ir,
"Previous invoice #%u no bolt12 (%.*s)",
*ir->inv->recurrence_counter - 1,
json_tok_full_len(arr + 1),
json_tok_full(buf, arr + 1));
}
previnv = invoice_decode(tmpctx, buf + b12->start, b12->end - b12->start,
plugin_feature_set(cmd->plugin),
chainparams, &fail);
if (!previnv) {
return fail_internalerr(cmd, ir,
"Previous invoice %.*s can't decode?",
json_tok_full_len(b12),
json_tok_full(buf, b12));
}
if (!previnv->recurrence_basetime) {
return fail_internalerr(cmd, ir,
"Previous invoice %.*s no recurrence_basetime?",
json_tok_full_len(b12), json_tok_full(buf, b12));
}
return check_period(cmd, ir, *previnv->recurrence_basetime);
}
/* Now, we need to check the previous invoice was paid, and maybe get timebase */
static struct command_result *check_previous_invoice(struct command *cmd,
struct invreq *ir)
{
struct out_req *req;
/* No previous? Just pass through */
if (*ir->invreq->recurrence_counter == 0)
return check_period(cmd, ir, *ir->inv->timestamp);
req = jsonrpc_request_start(cmd->plugin, cmd,
"listinvoices",
prev_invoice_done,
error,
ir);
json_add_label(req->js,
ir->invreq->offer_id,
ir->invreq->payer_key,
*ir->invreq->recurrence_counter - 1);
return send_outreq(cmd->plugin, req);
}
/* BOLT-offers #12:
* - MUST fail the request if `recurrence_signature` is not correct.
*/
static bool check_recurrence_sig(const struct tlv_invoice_request *invreq,
const struct pubkey32 *payer_key,
const struct bip340sig *sig)
{
struct sha256 merkle, sighash;
merkle_tlv(invreq->fields, &merkle);
sighash_from_merkle("invoice_request", "recurrence_signature",
&merkle, &sighash);
return secp256k1_schnorrsig_verify(secp256k1_ctx,
sig->u8,
sighash.u.u8, &payer_key->pubkey) == 1;
}
static struct command_result *invreq_amount_by_quantity(struct command *cmd,
const struct invreq *ir,
u64 *raw_amt)
{
assert(ir->offer->amount);
/* BOLT-offers #12:
* - MUST calculate the *base invoice amount* using the offer `amount`:
*/
*raw_amt = *ir->offer->amount;
/* BOLT-offers #12:
* - if request contains `quantity`, multiply by `quantity`.
*/
if (ir->invreq->quantity) {
if (mul_overflows_u64(*ir->invreq->quantity, *raw_amt)) {
return fail_invreq(cmd, ir,
"quantity %"PRIu64
" causes overflow",
*ir->invreq->quantity);
}
*raw_amt *= *ir->invreq->quantity;
}
return NULL;
}
/* The non-currency-converting case. */
static struct command_result *invreq_base_amount_simple(struct command *cmd,
const struct invreq *ir,
struct amount_msat *amt)
{
struct command_result *err;
if (ir->offer->amount) {
u64 raw_amount;
assert(!ir->offer->currency);
err = invreq_amount_by_quantity(cmd, ir, &raw_amount);
if (err)
return err;
*amt = amount_msat(raw_amount);
} else {
/* BOLT-offers #12:
*
* - otherwise:
* - MUST fail the request if it does not contain `amount`.
* - MUST use the request `amount` as the *base invoice amount*.
* (Note: invoice amount can be further modiifed by recurrence
* below)
*/
err = invreq_must_have(cmd, ir, amount);
if (err)
return err;
*amt = amount_msat(*ir->invreq->amount);
}
return NULL;
}
static struct command_result *handle_amount_and_recurrence(struct command *cmd,
struct invreq *ir,
struct amount_msat base_inv_amount)
{
/* BOLT-offers #12:
* - if the offer included `amount`:
*...
* - if the request contains `amount`:
* - MUST fail the request if its `amount` is less than the
* *base invoice amount*.
*/
if (ir->offer->amount && ir->invreq->amount) {
if (amount_msat_less(amount_msat(*ir->invreq->amount), base_inv_amount)) {
return fail_invreq(cmd, ir, "Amount must be at least %s",
type_to_string(tmpctx, struct amount_msat,
&base_inv_amount));
}
/* BOLT-offers #12:
* - MAY fail the request if its `amount` is much greater than
* the *base invoice amount*.
*/
/* Much == 5? Easier to divide and compare, than multiply. */
if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->amount), 5),
base_inv_amount)) {
return fail_invreq(cmd, ir, "Amount vastly exceeds %s",
type_to_string(tmpctx, struct amount_msat,
&base_inv_amount));
}
/* BOLT-offers #12:
* - MUST use the request's `amount` as the *base invoice
* amount*.
*/
base_inv_amount = amount_msat(*ir->invreq->amount);
}
/* This may be adjusted by recurrence if proportional_amount set */
ir->inv->amount = tal_dup(ir->inv, u64,
&base_inv_amount.millisatoshis); /* Raw: wire protocol */
/* Last of all, we handle recurrence details, which often requires
* further lookups. */
/* BOLT-offers #12:
* - MUST set (or not set) `recurrence_counter` exactly as the
* invoice_request did.
*/
if (ir->invreq->recurrence_counter) {
ir->inv->recurrence_counter = ir->invreq->recurrence_counter;
return check_previous_invoice(cmd, ir);
}
/* We're happy with 2 hours timeout (default): they can always
* request another. */
/* FIXME: Fallbacks? */
return create_invoicereq(cmd, ir);
}
static struct command_result *currency_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
const jsmntok_t *msat = json_get_member(buf, result, "msat");
struct amount_msat amount;
/* Fail in this case, forwarding warnings. */
if (!msat)
return fail_internalerr(cmd, ir,
"Cannot convert currency %.*s: %.*s",
(int)tal_bytelen(ir->offer->currency),
(const char *)ir->offer->currency,
json_tok_full_len(result),
json_tok_full(buf, result));
if (!json_to_msat(buf, msat, &amount))
return fail_internalerr(cmd, ir,
"Bad convert for currency %.*s: %.*s",
(int)tal_bytelen(ir->offer->currency),
(const char *)ir->offer->currency,
json_tok_full_len(msat),
json_tok_full(buf, msat));
return handle_amount_and_recurrence(cmd, ir, amount);
}
static struct command_result *convert_currency(struct command *cmd,
struct invreq *ir)
{
struct out_req *req;
u64 raw_amount;
double double_amount;
struct command_result *err;
const struct iso4217_name_and_divisor *iso4217;
assert(ir->offer->currency);
/* Multiply by quantity *first*, for best precision */
err = invreq_amount_by_quantity(cmd, ir, &raw_amount);
if (err)
return err;
/* BOLT-offers #12:
* - MUST calculate the *base invoice amount* using the offer
* `amount`:
* - if offer `currency` is not the invoice currency, convert
* to the invoice currency.
*/
iso4217 = find_iso4217(ir->offer->currency,
tal_bytelen(ir->offer->currency));
/* We should not create offer with unknown currency! */
if (!iso4217)
return fail_internalerr(cmd, ir,
"Unknown offer currency %.*s",
(int)tal_bytelen(ir->offer->currency),
ir->offer->currency);
double_amount = (double)raw_amount;
for (size_t i = 0; i < iso4217->minor_unit; i++)
double_amount /= 10;
req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert",
currency_done, error, ir);
json_add_stringn(req->js, "currency",
(const char *)ir->offer->currency,
tal_bytelen(ir->offer->currency));
json_add_member(req->js, "amount", false, "%f", double_amount);
return send_outreq(cmd->plugin, req);
}
static struct command_result *listoffers_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
const jsmntok_t *arr = json_get_member(buf, result, "offers");
const jsmntok_t *offertok, *activetok, *b12tok;
bool active;
char *fail;
struct command_result *err;
struct amount_msat amt;
/* BOLT-offers #12:
*
* - MUST fail the request if the `offer_id` does not refer to an
* unexpired offer.
*/
if (arr->size == 0)
return fail_invreq(cmd, ir, "Unknown offer");
offertok = arr + 1;
activetok = json_get_member(buf, offertok, "active");
if (!activetok) {
return fail_internalerr(cmd, ir,
"Missing active: %.*s",
json_tok_full_len(offertok),
json_tok_full(buf, offertok));
}
json_to_bool(buf, activetok, &active);
if (!active)
return fail_invreq(cmd, ir, "Offer no longer available");
b12tok = json_get_member(buf, offertok, "bolt12");
if (!b12tok) {
return fail_internalerr(cmd, ir,
"Missing bolt12: %.*s",
json_tok_full_len(offertok),
json_tok_full(buf, offertok));
}
ir->offer = offer_decode(ir,
buf + b12tok->start,
b12tok->end - b12tok->start,
plugin_feature_set(cmd->plugin),
chainparams, &fail);
if (!ir->offer) {
return fail_internalerr(cmd, ir,
"Invalid offer: %s (%.*s)",
fail,
json_tok_full_len(offertok),
json_tok_full(buf, offertok));
}
if (ir->offer->absolute_expiry
&& time_now().ts.tv_sec >= *ir->offer->absolute_expiry) {
/* FIXME: do deloffer to disable it */
return fail_invreq(cmd, ir, "Offer expired");
}
/* BOLT-offers #12:
* - if the offer had a `quantity_min` or `quantity_max` field:
* - MUST fail the request if there is no `quantity` field.
* - MUST fail the request if there is `quantity` is not within
* that (inclusive) range.
* - otherwise:
* - MUST fail the request if there is a `quantity` field.
*/
if (ir->offer->quantity_min || ir->offer->quantity_max) {
err = invreq_must_have(cmd, ir, quantity);
if (err)
return err;
if (ir->offer->quantity_min &&
*ir->invreq->quantity < *ir->offer->quantity_min) {
return fail_invreq(cmd, ir,
"quantity %"PRIu64 " < %"PRIu64,
*ir->invreq->quantity,
*ir->offer->quantity_min);
}
if (ir->offer->quantity_max &&
*ir->invreq->quantity > *ir->offer->quantity_max) {
return fail_invreq(cmd, ir,
"quantity %"PRIu64" > %"PRIu64,
*ir->invreq->quantity,
*ir->offer->quantity_max);
}
} else {
err = invreq_must_not_have(cmd, ir, quantity);
if (err)
return err;
}
if (ir->offer->recurrence) {
/* BOLT-offers #12:
*
* - if the offer had a `recurrence`:
* - MUST fail the request if there is no `recurrence_counter`
* field.
* - MUST fail the request if there is no
* `recurrence_signature` field.
* - MUST fail the request if `recurrence_signature` is not
* correct.
*/
err = invreq_must_have(cmd, ir, recurrence_counter);
if (err)
return err;
err = invreq_must_have(cmd, ir, recurrence_signature);
if (err)
return err;
if (!check_recurrence_sig(ir->invreq,
ir->invreq->payer_key,
ir->invreq->recurrence_signature)) {
return fail_invreq(cmd, ir,
"bad recurrence_signature");
}
} else {
/* BOLT-offers #12:
* - otherwise (the offer had no `recurrence`):
* - MUST fail the request if there is a `recurrence_counter`
* field.
* - MUST fail the request if there is a `recurrence_signature`
* field.
*/
err = invreq_must_not_have(cmd, ir, recurrence_counter);
if (err)
return err;
err = invreq_must_not_have(cmd, ir, recurrence_signature);
if (err)
return err;
}
ir->inv = tlv_invoice_new(cmd);
/* BOLT-offers #12:
* - if the chain for the invoice is not solely bitcoin:
* - MUST specify `chains` the offer is valid for.
*/
if (!streq(chainparams->network_name, "bitcoin")) {
ir->inv->chains = tal_arr(ir->inv, struct bitcoin_blkid, 1);
ir->inv->chains[0] = chainparams->genesis_blockhash;
}
/* BOLT-offers #12:
* - MUST set `offer_id` to the id of the offer.
*/
/* Which is the same as the invreq */
ir->inv->offer_id = tal_dup(ir->inv, struct sha256,
ir->invreq->offer_id);
ir->inv->description = tal_dup_talarr(ir->inv, char,
ir->offer->description);
ir->inv->features = tal_dup_talarr(ir->inv, u8,
plugin_feature_set(cmd->plugin)
->bits[BOLT11_FEATURE]);
/* FIXME: Insert paths and payinfo */
ir->inv->vendor = tal_dup_talarr(ir->inv, char, ir->offer->vendor);
ir->inv->node_id = tal_dup(ir->inv, struct pubkey32, ir->offer->node_id);
/* BOLT-offers #12:
* - MUST set (or not set) `quantity` exactly as the invoice_request
* did.
*/
if (ir->offer->quantity_min || ir->offer->quantity_max)
ir->inv->quantity = tal_dup(ir->inv, u64, ir->invreq->quantity);
/* BOLT-offers #12:
* - MUST set `payer_key` exactly as the invoice_request did.
*/
ir->inv->payer_key = tal_dup(ir->inv, struct pubkey32,
ir->invreq->payer_key);
/* BOLT-offers #12:
* - MUST set (or not set) `payer_info` exactly as the invoice_request
* did.
*/
ir->inv->payer_info
= tal_dup_talarr(ir->inv, u8, ir->invreq->payer_info);
randombytes_buf(&ir->preimage, sizeof(ir->preimage));
ir->inv->payment_hash = tal(ir->inv, struct sha256);
sha256(ir->inv->payment_hash, &ir->preimage, sizeof(ir->preimage));
ir->inv->cltv = tal_dup(ir->inv, u32, &cltv_final);
ir->inv->timestamp = tal(ir->inv, u64);
*ir->inv->timestamp = time_now().ts.tv_sec;
/* We may require currency lookup; if so, do it now. */
if (ir->offer->amount && ir->offer->currency)
return convert_currency(cmd, ir);
err = invreq_base_amount_simple(cmd, ir, &amt);
if (err)
return err;
return handle_amount_and_recurrence(cmd, ir, amt);
}
static struct command_result *handle_offerless_request(struct command *cmd,
struct invreq *ir)
{
/* FIXME: shut up and take their money! */
return fail_internalerr(cmd, ir, "FIXME: handle offerless req!");
}
struct command_result *handle_invoice_request(struct command *cmd,
const char *buf,
const jsmntok_t *invreqtok,
const jsmntok_t *replytok)
{
const u8 *invreqbin = json_tok_bin_from_hex(cmd, buf, invreqtok);
size_t len = tal_count(invreqbin);
struct invreq *ir = tal(cmd, struct invreq);
struct out_req *req;
int bad_feature;
/* Make a copy of entire buffer, for later. */
ir->buf = tal_dup_arr(ir, char, buf, replytok->end, 0);
ir->replytok = replytok;
ir->invreq = tlv_invoice_request_new(cmd);
if (!fromwire_invoice_request(&invreqbin, &len, ir->invreq)) {
return fail_invreq(cmd, ir,
"Invalid invreq %s",
tal_hex(tmpctx, invreqbin));
}
/* BOLT-offers #12:
*
* The reader of an invoice_request:
*...
* - MUST fail the request if `features` contains unknown even bits.
*/
bad_feature = features_unsupported(plugin_feature_set(cmd->plugin),
ir->invreq->features,
BOLT11_FEATURE);
if (bad_feature != -1) {
return fail_invreq(cmd, ir,
"Unsupported invreq feature %i",
bad_feature);
}
/* BOLT-offers #12:
*
* The reader of an invoice_request:
*...
* - MUST fail the request if `chains` does not include (or imply) a
* supported chain.
*/
if (!bolt12_chains_match(ir->invreq->chains, chainparams)) {
return fail_invreq(cmd, ir,
"Wrong chains %s",
tal_hex(tmpctx, ir->invreq->chains));
}
/* BOLT-offers #12:
*
* The reader of an invoice_request:
* - MUST fail the request if `payer_key` is not present.
*/
if (!ir->invreq->payer_key)
return fail_invreq(cmd, ir, "Missing payer key");
if (!ir->invreq->offer_id)
return handle_offerless_request(cmd, ir);
/* Now, look up offer */
req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers",
listoffers_done, error, ir);
json_add_sha256(req->js, "offer_id", ir->invreq->offer_id);
return send_outreq(cmd->plugin, req);
}