mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +01:00
offers: remove 'send-invoice' offers support.
This has radically changed in the spec, so remove it now, and we'll reintroduce / rewrite it. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
3afa5077fe
commit
846a520bc2
7 changed files with 3 additions and 998 deletions
|
@ -31,7 +31,7 @@ PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c
|
|||
PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h
|
||||
PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o)
|
||||
|
||||
PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c
|
||||
PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c
|
||||
PLUGIN_OFFERS_OBJS := $(PLUGIN_OFFERS_SRC:.c=.o)
|
||||
PLUGIN_OFFERS_HEADER := $(PLUGIN_OFFERS_SRC:.c=.h)
|
||||
|
||||
|
|
|
@ -751,32 +751,6 @@ static struct command_result *send_message(struct command *cmd,
|
|||
return make_reply_path(cmd, sending);
|
||||
}
|
||||
|
||||
/* We've received neither a reply nor a payment; return failure. */
|
||||
static void timeout_sent_inv(struct sent *sent)
|
||||
{
|
||||
struct json_out *details = json_out_new(sent);
|
||||
|
||||
json_out_start(details, NULL, '{');
|
||||
json_out_addstr(details, "invstring", invoice_encode(tmpctx, sent->inv));
|
||||
json_out_end(details, '}');
|
||||
|
||||
/* This will free sent! */
|
||||
discard_result(command_done_err(sent->cmd, OFFER_TIMEOUT,
|
||||
"Failed: timeout waiting for response",
|
||||
details));
|
||||
}
|
||||
|
||||
static struct command_result *prepare_inv_timeout(struct command *cmd,
|
||||
const char *buf UNUSED,
|
||||
const jsmntok_t *result UNUSED,
|
||||
struct sent *sent)
|
||||
{
|
||||
tal_steal(cmd, plugin_timer(cmd->plugin,
|
||||
time_from_sec(sent->wait_timeout),
|
||||
timeout_sent_inv, sent));
|
||||
return sendonionmsg_done(cmd, buf, result, sent);
|
||||
}
|
||||
|
||||
/* We've connected (if we tried), so send the invreq. */
|
||||
static struct command_result *
|
||||
sendinvreq_after_connect(struct command *cmd,
|
||||
|
@ -1276,354 +1250,6 @@ static struct command_result *invoice_payment(struct command *cmd,
|
|||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
/* We've connected (if we tried), so send the invoice. */
|
||||
static struct command_result *
|
||||
sendinvoice_after_connect(struct command *cmd,
|
||||
const char *buf UNUSED,
|
||||
const jsmntok_t *result UNUSED,
|
||||
struct sent *sent)
|
||||
{
|
||||
struct tlv_onionmsg_tlv *payload = tlv_onionmsg_tlv_new(sent);
|
||||
|
||||
payload->invoice = tal_arr(payload, u8, 0);
|
||||
towire_tlv_invoice(&payload->invoice, sent->inv);
|
||||
|
||||
return send_message(cmd, sent, payload, prepare_inv_timeout);
|
||||
}
|
||||
|
||||
static struct command_result *createinvoice_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *invtok = json_get_member(buf, result, "bolt12");
|
||||
char *fail;
|
||||
|
||||
/* Replace invoice with signed one */
|
||||
tal_free(sent->inv);
|
||||
sent->inv = invoice_decode(sent,
|
||||
buf + invtok->start,
|
||||
invtok->end - invtok->start,
|
||||
plugin_feature_set(cmd->plugin),
|
||||
chainparams,
|
||||
&fail);
|
||||
if (!sent->inv) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Bad createinvoice %.*s: %s",
|
||||
json_tok_full_len(invtok),
|
||||
json_tok_full(buf, invtok),
|
||||
fail);
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Bad createinvoice response %s", fail);
|
||||
}
|
||||
|
||||
sent->path = path_to_node(sent, cmd->plugin,
|
||||
sent->offer->node_id);
|
||||
if (!sent->path)
|
||||
return connect_direct(cmd, sent->offer->node_id,
|
||||
sendinvoice_after_connect, sent);
|
||||
|
||||
return sendinvoice_after_connect(cmd, NULL, NULL, sent);
|
||||
}
|
||||
|
||||
static struct command_result *sign_invoice(struct command *cmd,
|
||||
struct sent *sent)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
/* Get invoice signature and put in db so we can receive payment */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice",
|
||||
&createinvoice_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_string(req->js, "invstring", invoice_encode(tmpctx, sent->inv));
|
||||
json_add_preimage(req->js, "preimage", &sent->inv_preimage);
|
||||
json_add_escaped_string(req->js, "label", sent->inv_label);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static bool json_to_bip340sig(const char *buffer, const jsmntok_t *tok,
|
||||
struct bip340sig *sig)
|
||||
{
|
||||
return hex_decode(buffer + tok->start, tok->end - tok->start,
|
||||
sig->u8, sizeof(sig->u8));
|
||||
}
|
||||
|
||||
static struct command_result *payersign_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *sig;
|
||||
|
||||
sent->inv->refund_signature = tal(sent->inv, struct bip340sig);
|
||||
sig = json_get_member(buf, result, "signature");
|
||||
json_to_bip340sig(buf, sig, sent->inv->refund_signature);
|
||||
|
||||
return sign_invoice(cmd, sent);
|
||||
}
|
||||
|
||||
/* They're offering a refund, so we need to sign with same key as used
|
||||
* in initial payment. */
|
||||
static struct command_result *listsendpays_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct sent *sent)
|
||||
{
|
||||
const jsmntok_t *t, *arr = json_get_member(buf, result, "payments");
|
||||
size_t i;
|
||||
const u8 *public_tweak = NULL, *p;
|
||||
u8 *msg;
|
||||
size_t len;
|
||||
struct sha256 merkle;
|
||||
struct out_req *req;
|
||||
|
||||
/* Linearize populates ->fields */
|
||||
msg = tal_arr(tmpctx, u8, 0);
|
||||
towire_tlv_invoice(&msg, sent->inv);
|
||||
p = msg;
|
||||
len = tal_bytelen(msg);
|
||||
sent->inv = fromwire_tlv_invoice(cmd, &p, &len);
|
||||
if (!sent->inv)
|
||||
plugin_err(cmd->plugin,
|
||||
"Could not remarshall %s", tal_hex(tmpctx, msg));
|
||||
|
||||
merkle_tlv(sent->inv->fields, &merkle);
|
||||
|
||||
json_for_each_arr(i, t, arr) {
|
||||
const jsmntok_t *b12tok;
|
||||
struct tlv_invoice *inv;
|
||||
char *fail;
|
||||
|
||||
b12tok = json_get_member(buf, t, "bolt12");
|
||||
if (!b12tok) {
|
||||
/* This could happen if they try to refund a bolt11 */
|
||||
plugin_log(cmd->plugin, LOG_UNUSUAL,
|
||||
"Not bolt12 string in %.*s?",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
continue;
|
||||
}
|
||||
|
||||
inv = invoice_decode(tmpctx, buf + b12tok->start,
|
||||
b12tok->end - b12tok->start,
|
||||
plugin_feature_set(cmd->plugin),
|
||||
chainparams,
|
||||
&fail);
|
||||
if (!inv) {
|
||||
plugin_log(cmd->plugin, LOG_BROKEN,
|
||||
"Bad bolt12 string in %.*s?",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(buf, t));
|
||||
continue;
|
||||
}
|
||||
|
||||
public_tweak = inv->payer_info;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!public_tweak)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Cannot find invoice %s for refund",
|
||||
type_to_string(tmpctx, struct sha256,
|
||||
sent->offer->refund_for));
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `refund_signature` to the signature of the
|
||||
* `refunded_payment_hash` using prefix `refund_signature` and the
|
||||
* `payer_key` from the to-be-refunded invoice.
|
||||
*/
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "payersign",
|
||||
&payersign_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_string(req->js, "messagename", "invoice");
|
||||
json_add_string(req->js, "fieldname", "refund_signature");
|
||||
json_add_sha256(req->js, "merkle", &merkle);
|
||||
json_add_hex_talarr(req->js, "tweak", public_tweak);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *json_sendinvoice(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct amount_msat *msat;
|
||||
struct out_req *req;
|
||||
u32 *timeout;
|
||||
struct sent *sent = tal(cmd, struct sent);
|
||||
|
||||
sent->inv = tlv_invoice_new(cmd);
|
||||
sent->invreq = NULL;
|
||||
sent->cmd = cmd;
|
||||
|
||||
/* FIXME: Support recurring send_invoice offers? */
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("offer", param_offer, &sent->offer),
|
||||
p_req("label", param_label, &sent->inv_label),
|
||||
p_opt("amount_msat|msatoshi", param_msat, &msat),
|
||||
p_opt_def("timeout", param_number, &timeout, 90),
|
||||
p_opt("quantity", param_u64, &sent->inv->quantity),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
/* This is how long we'll wait for a reply for. */
|
||||
sent->wait_timeout = *timeout;
|
||||
|
||||
/* Check they are really trying to send us money. */
|
||||
if (!sent->offer->send_invoice)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer wants an invoice_request, not invoice");
|
||||
|
||||
/* If they don't tell us how much, base it on offer. */
|
||||
if (!msat) {
|
||||
if (sent->offer->currency)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer in different currency: need amount");
|
||||
if (!sent->offer->amount)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Offer did not specify: need amount");
|
||||
sent->inv->amount = tal_dup(sent->inv, u64, sent->offer->amount);
|
||||
if (sent->inv->quantity)
|
||||
*sent->inv->amount *= *sent->inv->quantity;
|
||||
} else
|
||||
sent->inv->amount = tal_dup(sent->inv, u64,
|
||||
&msat->millisatoshis); /* Raw: tlv */
|
||||
|
||||
/* FIXME: Support blinded paths, in which case use fake nodeid */
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - otherwise (responding to a `send_invoice` offer):
|
||||
* - MUST set `node_id` to the id of the node to send payment to.
|
||||
* - MUST set `description` the same as the offer.
|
||||
*/
|
||||
sent->inv->node_id = tal(sent->inv, struct pubkey);
|
||||
sent->inv->node_id->pubkey = local_id.pubkey;
|
||||
|
||||
sent->inv->description
|
||||
= tal_dup_talarr(sent->inv, char, sent->offer->description);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set (or not set) `send_invoice` the same as the offer.
|
||||
*/
|
||||
sent->inv->send_invoice = tal(sent->inv, struct tlv_invoice_send_invoice);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `offer_id` to the id of the offer.
|
||||
*/
|
||||
sent->inv->offer_id = tal(sent->inv, struct sha256);
|
||||
merkle_tlv(sent->offer->fields, sent->inv->offer_id);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - SHOULD not respond to an offer if the current time is after
|
||||
* `absolute_expiry`.
|
||||
*/
|
||||
if (sent->offer->absolute_expiry
|
||||
&& time_now().ts.tv_sec > *sent->offer->absolute_expiry)
|
||||
return command_fail(cmd, OFFER_EXPIRED, "Offer expired");
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - otherwise (responding to a `send_invoice` offer):
|
||||
*...
|
||||
* - if the offer had a `quantity_min` or `quantity_max` field:
|
||||
* - MUST set `quantity`
|
||||
* - MUST set it within that (inclusive) range.
|
||||
* - otherwise:
|
||||
* - MUST NOT set `quantity`
|
||||
*/
|
||||
if (sent->offer->quantity_min || sent->offer->quantity_max) {
|
||||
if (!sent->inv->quantity)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity parameter required");
|
||||
if (sent->offer->quantity_min
|
||||
&& *sent->inv->quantity < *sent->offer->quantity_min)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity must be >= %"PRIu64,
|
||||
*sent->offer->quantity_min);
|
||||
if (sent->offer->quantity_max
|
||||
&& *sent->inv->quantity > *sent->offer->quantity_max)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity must be <= %"PRIu64,
|
||||
*sent->offer->quantity_max);
|
||||
} else {
|
||||
if (sent->inv->quantity)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"quantity parameter unnecessary");
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `created_at` to the number of seconds since Midnight 1
|
||||
* January 1970, UTC when the offer was created.
|
||||
*/
|
||||
sent->inv->created_at = tal(sent->inv, u64);
|
||||
*sent->inv->created_at = time_now().ts.tv_sec;
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - if the expiry for accepting payment is not 7200 seconds after
|
||||
* `created_at`:
|
||||
* - MUST set `relative_expiry` `seconds_from_creation` to the number
|
||||
* of seconds after `created_at` that payment of this invoice should
|
||||
* not be attempted.
|
||||
*/
|
||||
if (sent->wait_timeout != 7200) {
|
||||
sent->inv->relative_expiry = tal(sent->inv, u32);
|
||||
*sent->inv->relative_expiry = sent->wait_timeout;
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `payer_key` to the `node_id` of the offer.
|
||||
*/
|
||||
sent->inv->payer_key = sent->offer->node_id;
|
||||
|
||||
/* FIXME: recurrence? */
|
||||
if (sent->offer->recurrence)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"FIXME: handle recurring send_invoice offer!");
|
||||
|
||||
/* BOLT-offers #12:
|
||||
*
|
||||
* - if the chain for the invoice is not solely bitcoin:
|
||||
* - MUST specify `chains` the offer is valid for.
|
||||
* - otherwise:
|
||||
* - the bitcoin chain is implied as the first and only entry.
|
||||
*/
|
||||
if (!streq(chainparams->network_name, "bitcoin")) {
|
||||
sent->inv->chain = tal_dup(sent->inv, struct bitcoin_blkid,
|
||||
&chainparams->genesis_blockhash);
|
||||
}
|
||||
|
||||
sent->inv->features
|
||||
= plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE];
|
||||
|
||||
randombytes_buf(&sent->inv_preimage, sizeof(sent->inv_preimage));
|
||||
sent->inv->payment_hash = tal(sent->inv, struct sha256);
|
||||
sha256(sent->inv->payment_hash,
|
||||
&sent->inv_preimage, sizeof(sent->inv_preimage));
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set (or not set) `refund_for` exactly as the offer did.
|
||||
* - if it sets `refund_for`:
|
||||
* - MUST set `refund_signature` to the signature of the
|
||||
* `refunded_payment_hash` using prefix `refund_signature` and
|
||||
* the `payer_key` from the to-be-refunded invoice.
|
||||
* - otherwise:
|
||||
* - MUST NOT set `refund_signature`
|
||||
*/
|
||||
if (sent->offer->refund_for) {
|
||||
sent->inv->refund_for = sent->offer->refund_for;
|
||||
/* Find original payment invoice */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays",
|
||||
&listsendpays_done,
|
||||
&forward_error,
|
||||
sent);
|
||||
json_add_sha256(req->js, "payment_hash",
|
||||
sent->offer->refund_for);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
return sign_invoice(cmd, sent);
|
||||
}
|
||||
|
||||
#if DEVELOPER
|
||||
static struct command_result *param_invreq(struct command *cmd,
|
||||
const char *name,
|
||||
|
@ -1682,13 +1308,6 @@ static const struct plugin_command commands[] = {
|
|||
NULL,
|
||||
json_fetchinvoice,
|
||||
},
|
||||
{
|
||||
"sendinvoice",
|
||||
"payment",
|
||||
"Request remote node for to pay this send_invoice {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.",
|
||||
NULL,
|
||||
json_sendinvoice,
|
||||
},
|
||||
#if DEVELOPER
|
||||
{
|
||||
"dev-rawrequest",
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <common/json_param.h>
|
||||
#include <common/json_stream.h>
|
||||
#include <plugins/offers.h>
|
||||
#include <plugins/offers_inv_hook.h>
|
||||
#include <plugins/offers_invreq_hook.h>
|
||||
#include <plugins/offers_offer.h>
|
||||
|
||||
|
@ -90,7 +89,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
|
|||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
const jsmntok_t *om, *replytok, *invreqtok, *invtok;
|
||||
const jsmntok_t *om, *replytok, *invreqtok;
|
||||
struct blinded_path *reply_path = NULL;
|
||||
|
||||
if (!offers_enabled)
|
||||
|
@ -118,13 +117,6 @@ static struct command_result *onion_message_modern_call(struct command *cmd,
|
|||
"invoice_request without reply_path");
|
||||
}
|
||||
|
||||
invtok = json_get_member(buf, om, "invoice");
|
||||
if (invtok) {
|
||||
const u8 *invbin = json_tok_bin_from_hex(tmpctx, buf, invtok);
|
||||
if (invbin)
|
||||
return handle_invoice(cmd, invbin, reply_path);
|
||||
}
|
||||
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
|
@ -969,13 +961,6 @@ static const struct plugin_command commands[] = {
|
|||
"Create an offer for invoices of {amount} with {description}, optional {issuer}, internal {label}, {quantity_min}, {quantity_max}, {absolute_expiry}, {recurrence}, {recurrence_base}, {recurrence_paywindow}, {recurrence_limit} and {single_use}",
|
||||
json_offer
|
||||
},
|
||||
{
|
||||
"offerout",
|
||||
"payment",
|
||||
"Create an offer to send money",
|
||||
"Create an offer to pay invoices of {amount} with {description}, optional {issuer}, internal {label}, {absolute_expiry} and {refund_for}",
|
||||
json_offerout
|
||||
},
|
||||
{
|
||||
"decode",
|
||||
"utility",
|
||||
|
|
|
@ -1,401 +0,0 @@
|
|||
#include "config.h"
|
||||
#include <ccan/mem/mem.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <common/bolt12_merkle.h>
|
||||
#include <common/json_stream.h>
|
||||
#include <common/type_to_string.h>
|
||||
#include <plugins/offers.h>
|
||||
#include <plugins/offers_inv_hook.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
/* We need to keep the reply path around so we can reply if error */
|
||||
struct inv {
|
||||
struct tlv_invoice *inv;
|
||||
|
||||
/* May be NULL */
|
||||
struct blinded_path *reply_path;
|
||||
|
||||
/* The offer, once we've looked it up. */
|
||||
struct tlv_offer *offer;
|
||||
};
|
||||
|
||||
static struct command_result *WARN_UNUSED_RESULT
|
||||
fail_inv_level(struct command *cmd,
|
||||
const struct inv *inv,
|
||||
enum log_level l,
|
||||
const char *fmt, va_list ap)
|
||||
{
|
||||
char *full_fmt, *msg;
|
||||
struct tlv_onionmsg_tlv *payload;
|
||||
struct tlv_invoice_error *err;
|
||||
|
||||
full_fmt = tal_fmt(tmpctx, "Failed invoice");
|
||||
if (inv->inv) {
|
||||
tal_append_fmt(&full_fmt, " %s",
|
||||
invoice_encode(tmpctx, inv->inv));
|
||||
if (inv->inv->offer_id)
|
||||
tal_append_fmt(&full_fmt, " for offer %s",
|
||||
type_to_string(tmpctx, struct sha256,
|
||||
inv->inv->offer_id));
|
||||
}
|
||||
tal_append_fmt(&full_fmt, ": %s", fmt);
|
||||
|
||||
msg = tal_vfmt(tmpctx, full_fmt, ap);
|
||||
plugin_log(cmd->plugin, l, "%s", msg);
|
||||
|
||||
/* Only reply if they gave us a path */
|
||||
if (!inv->reply_path)
|
||||
return command_hook_success(cmd);
|
||||
|
||||
/* 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! */
|
||||
|
||||
payload = tlv_onionmsg_tlv_new(tmpctx);
|
||||
payload->invoice_error = tal_arr(payload, u8, 0);
|
||||
towire_tlv_invoice_error(&payload->invoice_error, err);
|
||||
return send_onion_reply(cmd, inv->reply_path, payload);
|
||||
}
|
||||
|
||||
static struct command_result *WARN_UNUSED_RESULT
|
||||
fail_inv(struct command *cmd,
|
||||
const struct inv *inv,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
struct command_result *ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = fail_inv_level(cmd, inv, LOG_DBG, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct command_result *WARN_UNUSED_RESULT
|
||||
fail_internalerr(struct command *cmd,
|
||||
const struct inv *inv,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
struct command_result *ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = fail_inv_level(cmd, inv, LOG_BROKEN, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define inv_must_have(cmd_, i_, fld_) \
|
||||
test_field(cmd_, i_, i_->inv->fld_ != NULL, #fld_, "missing")
|
||||
#define inv_must_not_have(cmd_, i_, fld_) \
|
||||
test_field(cmd_, i_, i_->inv->fld_ == NULL, #fld_, "unexpected")
|
||||
#define inv_must_equal_offer(cmd_, i_, fld_) \
|
||||
test_field_eq(cmd_, i_, i_->inv->fld_, i_->offer->fld_, #fld_)
|
||||
|
||||
static struct command_result *
|
||||
test_field(struct command *cmd,
|
||||
const struct inv *inv,
|
||||
bool test, const char *fieldname, const char *what)
|
||||
{
|
||||
if (!test)
|
||||
return fail_inv(cmd, inv, "%s %s", what, fieldname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct command_result *
|
||||
test_field_eq(struct command *cmd,
|
||||
const struct inv *inv,
|
||||
const tal_t *invfield,
|
||||
const tal_t *offerfield,
|
||||
const char *fieldname)
|
||||
{
|
||||
if (invfield && !offerfield)
|
||||
return fail_inv(cmd, inv, "Unexpected %s", fieldname);
|
||||
if (!invfield && offerfield)
|
||||
return fail_inv(cmd, inv, "Expected %s", fieldname);
|
||||
if (!memeq(invfield, tal_bytelen(invfield),
|
||||
offerfield, tal_bytelen(offerfield)))
|
||||
return fail_inv(cmd, inv, "Different %s", fieldname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct command_result *pay_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct inv *inv)
|
||||
{
|
||||
struct amount_msat msat = amount_msat(*inv->inv->amount);
|
||||
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Payed out %s for offer %s%s: %.*s",
|
||||
type_to_string(tmpctx, struct amount_msat, &msat),
|
||||
type_to_string(tmpctx, struct sha256, inv->inv->offer_id),
|
||||
inv->offer->refund_for ? " (refund)": "",
|
||||
json_tok_full_len(result),
|
||||
json_tok_full(buf, result));
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *pay_error(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *error,
|
||||
struct inv *inv)
|
||||
{
|
||||
const jsmntok_t *msgtok = json_get_member(buf, error, "message");
|
||||
|
||||
return fail_inv(cmd, inv, "pay attempt failed: %.*s",
|
||||
json_tok_full_len(msgtok),
|
||||
json_tok_full(buf, msgtok));
|
||||
}
|
||||
|
||||
static struct command_result *listoffers_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct inv *inv)
|
||||
{
|
||||
const jsmntok_t *arr = json_get_member(buf, result, "offers");
|
||||
const jsmntok_t *offertok, *activetok, *b12tok;
|
||||
bool active;
|
||||
struct amount_msat amt;
|
||||
char *fail;
|
||||
struct out_req *req;
|
||||
struct command_result *err;
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - otherwise if `offer_id` is set:
|
||||
* - MUST reject the invoice if the `offer_id` does not refer an
|
||||
* unexpired offer with `send_invoice`
|
||||
*/
|
||||
if (arr->size == 0)
|
||||
return fail_inv(cmd, inv, "Unknown offer");
|
||||
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Attempting payment of offer %.*s",
|
||||
json_tok_full_len(result),
|
||||
json_tok_full(buf, result));
|
||||
|
||||
offertok = arr + 1;
|
||||
activetok = json_get_member(buf, offertok, "active");
|
||||
if (!activetok) {
|
||||
return fail_internalerr(cmd, inv,
|
||||
"Missing active: %.*s",
|
||||
json_tok_full_len(offertok),
|
||||
json_tok_full(buf, offertok));
|
||||
}
|
||||
json_to_bool(buf, activetok, &active);
|
||||
if (!active)
|
||||
return fail_inv(cmd, inv, "Offer no longer available");
|
||||
|
||||
b12tok = json_get_member(buf, offertok, "bolt12");
|
||||
if (!b12tok) {
|
||||
return fail_internalerr(cmd, inv,
|
||||
"Missing bolt12: %.*s",
|
||||
json_tok_full_len(offertok),
|
||||
json_tok_full(buf, offertok));
|
||||
}
|
||||
inv->offer = offer_decode(inv,
|
||||
buf + b12tok->start,
|
||||
b12tok->end - b12tok->start,
|
||||
plugin_feature_set(cmd->plugin),
|
||||
chainparams, &fail);
|
||||
if (!inv->offer) {
|
||||
return fail_internalerr(cmd, inv,
|
||||
"Invalid offer: %s (%.*s)",
|
||||
fail,
|
||||
json_tok_full_len(offertok),
|
||||
json_tok_full(buf, offertok));
|
||||
}
|
||||
|
||||
if (inv->offer->absolute_expiry
|
||||
&& time_now().ts.tv_sec >= *inv->offer->absolute_expiry) {
|
||||
/* FIXME: do deloffer to disable it */
|
||||
return fail_inv(cmd, inv, "Offer expired");
|
||||
}
|
||||
|
||||
if (!inv->offer->send_invoice) {
|
||||
return fail_inv(cmd, inv, "Offer did not expect invoice");
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST reject the invoice unless the following fields are equal
|
||||
* or unset exactly as they are in the `offer`:
|
||||
* - `refund_for`
|
||||
* - `description`
|
||||
*/
|
||||
err = inv_must_equal_offer(cmd, inv, refund_for);
|
||||
if (err)
|
||||
return err;
|
||||
err = inv_must_equal_offer(cmd, inv, description);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* 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 (inv->offer->quantity_min || inv->offer->quantity_max) {
|
||||
err = inv_must_have(cmd, inv, quantity);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (inv->offer->quantity_min &&
|
||||
*inv->inv->quantity < *inv->offer->quantity_min) {
|
||||
return fail_inv(cmd, inv,
|
||||
"quantity %"PRIu64 " < %"PRIu64,
|
||||
*inv->inv->quantity,
|
||||
*inv->offer->quantity_min);
|
||||
}
|
||||
|
||||
if (inv->offer->quantity_max &&
|
||||
*inv->inv->quantity > *inv->offer->quantity_max) {
|
||||
return fail_inv(cmd, inv,
|
||||
"quantity %"PRIu64" > %"PRIu64,
|
||||
*inv->inv->quantity,
|
||||
*inv->offer->quantity_max);
|
||||
}
|
||||
} else {
|
||||
err = inv_must_not_have(cmd, inv, quantity);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST reject the invoice if `msat` is not present.
|
||||
*/
|
||||
err = inv_must_have(cmd, inv, amount);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* FIXME: Handle alternate currency conversion here! */
|
||||
if (inv->offer->currency)
|
||||
return fail_inv(cmd, inv, "FIXME: support currency");
|
||||
|
||||
amt = amount_msat(*inv->inv->amount);
|
||||
/* If you send an offer without an amount, you want to give away
|
||||
* unlimited money. Err, ok? */
|
||||
if (inv->offer->amount) {
|
||||
struct amount_msat expected = amount_msat(*inv->offer->amount);
|
||||
|
||||
/* We could allow invoices for less, I suppose. */
|
||||
if (!amount_msat_eq(expected, amt))
|
||||
return fail_inv(cmd, inv, "Expected invoice for %s",
|
||||
fmt_amount_msat(tmpctx, expected));
|
||||
}
|
||||
|
||||
plugin_log(cmd->plugin, LOG_INFORM,
|
||||
"Attempting payment of %s for offer %s%s",
|
||||
type_to_string(tmpctx, struct amount_msat, &amt),
|
||||
type_to_string(tmpctx, struct sha256, inv->inv->offer_id),
|
||||
inv->offer->refund_for ? " (refund)": "");
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "pay",
|
||||
pay_done, pay_error, inv);
|
||||
json_add_string(req->js, "bolt11", invoice_encode(tmpctx, inv->inv));
|
||||
json_add_sha256(req->js, "localofferid", inv->inv->offer_id);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *listoffers_error(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *err,
|
||||
struct inv *inv)
|
||||
{
|
||||
return fail_internalerr(cmd, inv,
|
||||
"listoffers gave JSON error: %.*s",
|
||||
json_tok_full_len(err),
|
||||
json_tok_full(buf, err));
|
||||
}
|
||||
|
||||
struct command_result *handle_invoice(struct command *cmd,
|
||||
const u8 *invbin,
|
||||
struct blinded_path *reply_path STEALS)
|
||||
{
|
||||
size_t len = tal_count(invbin);
|
||||
struct inv *inv = tal(cmd, struct inv);
|
||||
struct out_req *req;
|
||||
struct command_result *err;
|
||||
int bad_feature;
|
||||
struct sha256 m, shash;
|
||||
|
||||
inv->reply_path = tal_steal(inv, reply_path);
|
||||
|
||||
inv->inv = fromwire_tlv_invoice(cmd, &invbin, &len);
|
||||
if (!inv->inv) {
|
||||
return fail_inv(cmd, inv,
|
||||
"Invalid invoice %s",
|
||||
tal_hex(tmpctx, invbin));
|
||||
}
|
||||
|
||||
/* 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),
|
||||
inv->inv->features,
|
||||
BOLT11_FEATURE);
|
||||
if (bad_feature != -1) {
|
||||
return fail_inv(cmd, inv,
|
||||
"Unsupported inv feature %i",
|
||||
bad_feature);
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
*
|
||||
* The reader of an invoice_request:
|
||||
*...
|
||||
* - if `chain` is not present:
|
||||
* - MUST fail the request if bitcoin is not a supported chain.
|
||||
* - otherwise:
|
||||
* - MUST fail the request if `chain` is not a supported chain.
|
||||
*/
|
||||
if (!bolt12_chain_matches(inv->inv->chain, chainparams)) {
|
||||
return fail_inv(cmd, inv,
|
||||
"Wrong chain %s",
|
||||
tal_hex(tmpctx, inv->inv->chain));
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST reject the invoice if `signature` is not a valid signature
|
||||
* using `node_id` as described in
|
||||
* [Signature Calculation](#signature-calculation).
|
||||
*/
|
||||
err = inv_must_have(cmd, inv, node_id);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = inv_must_have(cmd, inv, signature);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
merkle_tlv(inv->inv->fields, &m);
|
||||
sighash_from_merkle("invoice", "signature", &m, &shash);
|
||||
if (!check_schnorr_sig(&shash, &inv->inv->node_id->pubkey,
|
||||
inv->inv->signature)) {
|
||||
return fail_inv(cmd, inv, "Bad signature");
|
||||
}
|
||||
|
||||
/* We don't pay random invoices off the internet, sorry. */
|
||||
err = inv_must_have(cmd, inv, offer_id);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Now find the offer. */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers",
|
||||
listoffers_done, listoffers_error, inv);
|
||||
json_add_sha256(req->js, "offer_id", inv->inv->offer_id);
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#ifndef LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H
|
||||
#define LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H
|
||||
#include "config.h"
|
||||
#include <plugins/libplugin.h>
|
||||
|
||||
/* We got an onionmessage with an invoice! reply_path could be NULL. */
|
||||
struct command_result *handle_invoice(struct command *cmd,
|
||||
const u8 *invbin,
|
||||
struct blinded_path *reply_path STEALS);
|
||||
#endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */
|
|
@ -26,18 +26,6 @@ static bool msat_or_any(const char *buffer,
|
|||
return true;
|
||||
}
|
||||
|
||||
static struct command_result *param_msat_or_any(struct command *cmd,
|
||||
const char *name,
|
||||
const char *buffer,
|
||||
const jsmntok_t *tok,
|
||||
struct tlv_offer *offer)
|
||||
{
|
||||
if (msat_or_any(buffer, tok, offer))
|
||||
return NULL;
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
"should be 'any' or msatoshis");
|
||||
}
|
||||
|
||||
static struct command_result *param_amount(struct command *cmd,
|
||||
const char *name,
|
||||
const char *buffer,
|
||||
|
@ -225,32 +213,6 @@ static struct command_result *param_recurrence_paywindow(struct command *cmd,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct command_result *param_invoice_payment_hash(struct command *cmd,
|
||||
const char *name,
|
||||
const char *buffer,
|
||||
const jsmntok_t *tok,
|
||||
struct sha256 **hash)
|
||||
{
|
||||
struct tlv_invoice *inv;
|
||||
char *fail;
|
||||
|
||||
inv = invoice_decode(tmpctx, buffer + tok->start, tok->end - tok->start,
|
||||
plugin_feature_set(cmd->plugin), chainparams,
|
||||
&fail);
|
||||
if (!inv)
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
tal_fmt(cmd,
|
||||
"Unparsable invoice: %s",
|
||||
fail));
|
||||
|
||||
if (!inv->payment_hash)
|
||||
return command_fail_badparam(cmd, name, buffer, tok,
|
||||
"invoice missing payment_hash");
|
||||
|
||||
*hash = tal_steal(cmd, inv->payment_hash);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct offer_info {
|
||||
const struct tlv_offer *offer;
|
||||
const char *label;
|
||||
|
@ -424,61 +386,3 @@ struct command_result *json_offer(struct command *cmd,
|
|||
|
||||
return create_offer(cmd, offinfo);
|
||||
}
|
||||
|
||||
struct command_result *json_offerout(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
const char *desc, *issuer, *label;
|
||||
struct tlv_offer *offer;
|
||||
struct out_req *req;
|
||||
|
||||
offer = tlv_offer_new(cmd);
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("amount", param_msat_or_any, offer),
|
||||
p_req("description", param_escaped_string, &desc),
|
||||
p_opt("issuer", param_escaped_string, &issuer),
|
||||
p_opt("label", param_escaped_string, &label),
|
||||
p_opt("absolute_expiry", param_u64, &offer->absolute_expiry),
|
||||
p_opt("refund_for", param_invoice_payment_hash, &offer->refund_for),
|
||||
/* FIXME: hints support! */
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
if (!offers_enabled)
|
||||
return command_fail(cmd, LIGHTNINGD,
|
||||
"experimental-offers not enabled");
|
||||
|
||||
offer->send_invoice = tal(offer, struct tlv_offer_send_invoice);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
*
|
||||
* - if the chain for the invoice is not solely bitcoin:
|
||||
* - MUST specify `chains` the offer is valid for.
|
||||
* - otherwise:
|
||||
* - the bitcoin chain is implied as the first and only entry.
|
||||
*/
|
||||
if (!streq(chainparams->network_name, "bitcoin")) {
|
||||
offer->chains = tal_arr(offer, struct bitcoin_blkid, 1);
|
||||
offer->chains[0] = chainparams->genesis_blockhash;
|
||||
}
|
||||
|
||||
offer->description = tal_dup_arr(offer, char, desc, strlen(desc), 0);
|
||||
if (issuer)
|
||||
offer->issuer = tal_dup_arr(offer, char,
|
||||
issuer, strlen(issuer), 0);
|
||||
|
||||
offer->node_id = tal_dup(offer, struct pubkey, &id);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "createoffer",
|
||||
check_result, forward_error,
|
||||
offer);
|
||||
json_add_string(req->js, "bolt12", offer_encode(tmpctx, offer));
|
||||
if (label)
|
||||
json_add_string(req->js, "label", label);
|
||||
json_add_bool(req->js, "single_use", true);
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
|
|
|
@ -4379,9 +4379,6 @@ def test_offer_needs_option(node_factory):
|
|||
l1 = node_factory.get_node()
|
||||
with pytest.raises(RpcError, match='experimental-offers not enabled'):
|
||||
l1.rpc.call('offer', {'amount': '1msat', 'description': 'test'})
|
||||
with pytest.raises(RpcError, match='experimental-offers not enabled'):
|
||||
l1.rpc.call('offerout', {'amount': '2msat',
|
||||
'description': 'simple test'})
|
||||
with pytest.raises(RpcError, match='Unknown command'):
|
||||
l1.rpc.call('fetchinvoice', {'offer': 'aaaa'})
|
||||
|
||||
|
@ -4823,16 +4820,6 @@ def test_fetchinvoice_autoconnect(node_factory, bitcoind):
|
|||
l3.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
|
||||
assert l3.rpc.listpeers(l2.info['id'])['peers'] != []
|
||||
|
||||
# Similarly for send-invoice offer.
|
||||
l3.rpc.disconnect(l2.info['id'])
|
||||
offer = l2.rpc.call('offerout', {'amount': '2msat',
|
||||
'description': 'simple test'})
|
||||
# Ofc l2 can't actually pay it!
|
||||
with pytest.raises(RpcError, match='pay attempt failed: "Ran out of routes to try'):
|
||||
l3.rpc.call('sendinvoice', {'offer': offer['bolt12'], 'label': 'payme!'})
|
||||
|
||||
assert l3.rpc.listpeers(l2.info['id'])['peers'] != []
|
||||
|
||||
# But if we create a channel l3->l1->l2 (and balance!), l2 can!
|
||||
node_factory.join_nodes([l3, l1], wait_for_announce=True)
|
||||
# Make sure l2 knows about it
|
||||
|
@ -4843,7 +4830,7 @@ def test_fetchinvoice_autoconnect(node_factory, bitcoind):
|
|||
wait_for(lambda: only_one(only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['channels'])['spendable_msat'] != Millisatoshi(0))
|
||||
|
||||
l3.rpc.disconnect(l2.info['id'])
|
||||
l3.rpc.call('sendinvoice', {'offer': offer['bolt12'], 'label': 'payme for real!'})
|
||||
l3.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
|
||||
# It will have autoconnected, to send invoice (since l1 says it doesn't do onion messages!)
|
||||
assert l3.rpc.listpeers(l2.info['id'])['peers'] != []
|
||||
|
||||
|
@ -4883,85 +4870,6 @@ def test_dev_rawrequest(node_factory):
|
|||
assert 'invoice' in ret
|
||||
|
||||
|
||||
def test_sendinvoice(node_factory, bitcoind):
|
||||
l2opts = {'experimental-offers': None}
|
||||
l1, l2 = node_factory.line_graph(2, wait_for_announce=True,
|
||||
opts=[{'experimental-offers': None},
|
||||
l2opts])
|
||||
|
||||
# Simple offer to send money (balances channel a little)
|
||||
offer = l1.rpc.call('offerout', {'amount': '100000sat',
|
||||
'description': 'simple test'})
|
||||
|
||||
# Fetchinvoice will refuse, since you're supposed to send an invoice.
|
||||
with pytest.raises(RpcError, match='Offer wants an invoice, not invoice_request'):
|
||||
l2.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
|
||||
|
||||
# used will be false
|
||||
assert only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False
|
||||
|
||||
# sendinvoice should work.
|
||||
out = l2.rpc.call('sendinvoice', {'offer': offer['bolt12'],
|
||||
'label': 'test sendinvoice 1'})
|
||||
assert out['label'] == 'test sendinvoice 1'
|
||||
assert out['description'] == 'simple test'
|
||||
assert 'bolt12' in out
|
||||
assert 'payment_hash' in out
|
||||
assert out['status'] == 'paid'
|
||||
assert 'payment_preimage' in out
|
||||
assert 'expires_at' in out
|
||||
assert out['amount_msat'] == Millisatoshi(100000000)
|
||||
assert 'pay_index' in out
|
||||
assert out['amount_received_msat'] == Millisatoshi(100000000)
|
||||
|
||||
# Note, if we're slow, this fails with "Offer no longer available",
|
||||
# *but* if it hasn't heard about payment success yet, l2 will fail
|
||||
# simply because payments are already pending.
|
||||
with pytest.raises(RpcError, match='Offer no longer available|pay attempt failed'):
|
||||
l2.rpc.call('sendinvoice', {'offer': offer['bolt12'],
|
||||
'label': 'test sendinvoice 2'})
|
||||
|
||||
# Technically, l1 may not have gotten payment success, so we need to wait.
|
||||
wait_for(lambda: only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True)
|
||||
|
||||
# Now try a refund.
|
||||
offer = l2.rpc.call('offer', {'amount': '100msat',
|
||||
'description': 'simple test'})
|
||||
assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False
|
||||
|
||||
inv = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
|
||||
l1.rpc.pay(inv['invoice'])
|
||||
assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True
|
||||
|
||||
refund = l2.rpc.call('offerout', {'amount': '100msat',
|
||||
'description': 'refund test',
|
||||
'refund_for': inv['invoice']})
|
||||
assert only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is False
|
||||
|
||||
l1.rpc.call('sendinvoice', {'offer': refund['bolt12'],
|
||||
'label': 'test sendinvoice refund'})
|
||||
wait_for(lambda: only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is True)
|
||||
|
||||
# Offer with issuer: we must not copy issuer into our invoice!
|
||||
offer = l1.rpc.call('offerout', {'amount': '10000sat',
|
||||
'description': 'simple test',
|
||||
'issuer': "clightning test suite"})
|
||||
|
||||
out = l2.rpc.call('sendinvoice', {'offer': offer['bolt12'],
|
||||
'label': 'test sendinvoice 3'})
|
||||
assert out['label'] == 'test sendinvoice 3'
|
||||
assert out['description'] == 'simple test'
|
||||
assert 'issuer' not in out
|
||||
assert 'bolt12' in out
|
||||
assert 'payment_hash' in out
|
||||
assert out['status'] == 'paid'
|
||||
assert 'payment_preimage' in out
|
||||
assert 'expires_at' in out
|
||||
assert out['amount_msat'] == Millisatoshi(10000000)
|
||||
assert 'pay_index' in out
|
||||
assert out['amount_received_msat'] == Millisatoshi(10000000)
|
||||
|
||||
|
||||
def test_self_pay(node_factory):
|
||||
"""Repro test for issue 4345: pay ourselves via the pay plugin.
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue