mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-23 17:14:22 +01:00
plugins/fetchinvoice: handle sendinvoice timeout, error or payment.
If they pay the invoice, they don't bother replying; that's just for errors. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
@@ -83,6 +83,7 @@ static const errcode_t OFFER_ALREADY_DISABLED = 1001;
|
||||
static const errcode_t OFFER_EXPIRED = 1002;
|
||||
static const errcode_t OFFER_ROUTE_NOT_FOUND = 1003;
|
||||
static const errcode_t OFFER_BAD_INVREQ_REPLY = 1004;
|
||||
static const errcode_t OFFER_TIMEOUT = 1005;
|
||||
|
||||
/* Errors from wait* commands */
|
||||
static const errcode_t WAIT_TIMEOUT = 2000;
|
||||
|
||||
@@ -44,6 +44,8 @@ struct sent {
|
||||
struct tlv_invoice *inv;
|
||||
struct preimage inv_preimage;
|
||||
struct json_escape *inv_label;
|
||||
/* How long to wait for response before giving up. */
|
||||
u32 inv_wait_timeout;
|
||||
};
|
||||
|
||||
static struct sent *find_sent(const struct pubkey *blinding)
|
||||
@@ -371,15 +373,6 @@ badinv:
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *handle_inv_response(struct command *cmd,
|
||||
struct sent *sent,
|
||||
const char *buf,
|
||||
const jsmntok_t *om)
|
||||
{
|
||||
/* FIXME: Report error. */
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *recv_onion_message(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
@@ -413,10 +406,8 @@ static struct command_result *recv_onion_message(struct command *cmd,
|
||||
|
||||
if (sent->invreq)
|
||||
return handle_invreq_response(cmd, sent, buf, om);
|
||||
else {
|
||||
assert(sent->inv);
|
||||
return handle_inv_response(cmd, sent, buf, om);
|
||||
}
|
||||
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static void destroy_sent(struct sent *sent)
|
||||
@@ -620,6 +611,31 @@ static struct command_result *send_message(struct command *cmd,
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
/* 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_addstr(details, "invstring", invoice_encode(tmpctx, sent->inv));
|
||||
/* This will free sent! */
|
||||
discard_result(command_done_err(sent->cmd, OFFER_TIMEOUT,
|
||||
"Timeout waiting for response"
|
||||
" (but use waitinvoice if invoice_timeout"
|
||||
" was greater)",
|
||||
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->inv_wait_timeout),
|
||||
timeout_sent_inv, sent));
|
||||
return sendonionmsg_done(cmd, buf, result, sent);
|
||||
}
|
||||
|
||||
static struct command_result *invreq_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
@@ -851,6 +867,54 @@ static struct command_result *json_fetchinvoice(struct command *cmd,
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
/* FIXME: Using a hook here is not ideal: technically it doesn't mean
|
||||
* it's actually hit the db! But using waitinvoice is also suboptimal
|
||||
* because we don't have libplugin infra to cancel a pending req (and I
|
||||
* want to rewrite our wait* API anyway) */
|
||||
static struct command_result *invoice_payment(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct sent *i;
|
||||
const jsmntok_t *ptok, *preimagetok, *msattok;
|
||||
struct preimage preimage;
|
||||
struct amount_msat msat;
|
||||
|
||||
ptok = json_get_member(buf, params, "payment");
|
||||
preimagetok = json_get_member(buf, ptok, "preimage");
|
||||
msattok = json_get_member(buf, ptok, "msat");
|
||||
if (!preimagetok || !msattok)
|
||||
plugin_err(cmd->plugin,
|
||||
"Invalid invoice_payment %.*s",
|
||||
json_tok_full_len(params),
|
||||
json_tok_full(buf, params));
|
||||
|
||||
hex_decode(buf + preimagetok->start,
|
||||
preimagetok->end - preimagetok->start,
|
||||
&preimage, sizeof(preimage));
|
||||
json_to_msat(buf, msattok, &msat);
|
||||
|
||||
list_for_each(&sent_list, i, list) {
|
||||
struct json_stream *out;
|
||||
|
||||
if (!i->inv)
|
||||
continue;
|
||||
if (!preimage_eq(&preimage, &i->inv_preimage))
|
||||
continue;
|
||||
|
||||
/* It was paid! Success. */
|
||||
/* FIXME: Return as per waitinvoice */
|
||||
out = jsonrpc_stream_success(i->cmd);
|
||||
json_add_string(out, "invstring", invoice_encode(tmpctx, i->inv));
|
||||
json_add_string(out, "msat",
|
||||
type_to_string(tmpctx, struct amount_msat,
|
||||
&msat));
|
||||
discard_result(command_finished(i->cmd, out));
|
||||
break;
|
||||
}
|
||||
return command_hook_success(cmd);
|
||||
}
|
||||
|
||||
static struct command_result *createinvoice_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
@@ -880,7 +944,7 @@ static struct command_result *createinvoice_done(struct command *cmd,
|
||||
|
||||
rawinv = tal_arr(tmpctx, u8, 0);
|
||||
towire_invoice(&rawinv, sent->inv);
|
||||
return send_message(cmd, sent, "invoice", rawinv, sendonionmsg_done);
|
||||
return send_message(cmd, sent, "invoice", rawinv, prepare_inv_timeout);
|
||||
}
|
||||
|
||||
static struct command_result *sign_invoice(struct command *cmd,
|
||||
@@ -1007,6 +1071,7 @@ static struct command_result *json_sendinvoice(struct command *cmd,
|
||||
{
|
||||
struct amount_msat *msat;
|
||||
struct out_req *req;
|
||||
u32 *timeout, *invoice_timeout;
|
||||
struct sent *sent = tal(cmd, struct sent);
|
||||
|
||||
sent->inv = tlv_invoice_new(cmd);
|
||||
@@ -1018,10 +1083,15 @@ static struct command_result *json_sendinvoice(struct command *cmd,
|
||||
p_req("offer", param_offer, &sent->offer),
|
||||
p_req("label", param_label, &sent->inv_label),
|
||||
p_opt("msatoshi", param_msat, &msat),
|
||||
p_opt_def("timeout", param_number, &timeout, 90),
|
||||
p_opt("invoice_timeout", param_number, &invoice_timeout),
|
||||
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->inv_wait_timeout = *timeout;
|
||||
|
||||
/* Check they are really trying to send us money. */
|
||||
if (!sent->offer->send_invoice)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
@@ -1105,6 +1175,34 @@ static struct command_result *json_sendinvoice(struct command *cmd,
|
||||
"quantity parameter unnecessary");
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `timestamp` to the number of seconds since Midnight 1
|
||||
* January 1970, UTC.
|
||||
*/
|
||||
sent->inv->timestamp = tal(sent->inv, u64);
|
||||
*sent->inv->timestamp = time_now().ts.tv_sec;
|
||||
|
||||
/* If they don't specify an invoice_timeout, make it the same as we're
|
||||
* prepare to wait. */
|
||||
if (!invoice_timeout)
|
||||
invoice_timeout = timeout;
|
||||
else if (*invoice_timeout < *timeout)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"invoice_timeout %u must be >= timeout %u",
|
||||
*invoice_timeout, *timeout);
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - if the expiry for accepting payment is not 7200 seconds after
|
||||
* `timestamp`:
|
||||
* - MUST set `relative_expiry` `seconds_from_timestamp` to the number
|
||||
* of seconds after `timestamp` that payment of this invoice should
|
||||
* not be attempted.
|
||||
*/
|
||||
if (*invoice_timeout != 7200) {
|
||||
sent->inv->relative_expiry = tal(sent->inv, u32);
|
||||
*sent->inv->relative_expiry = *invoice_timeout;
|
||||
}
|
||||
|
||||
/* BOLT-offers #12:
|
||||
* - MUST set `payer_key` to the `node_id` of the offer.
|
||||
*/
|
||||
@@ -1191,6 +1289,10 @@ static const struct plugin_hook hooks[] = {
|
||||
"onion_message_blinded",
|
||||
recv_onion_message
|
||||
},
|
||||
{
|
||||
"invoice_payment",
|
||||
invoice_payment,
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
||||
@@ -3967,3 +3967,29 @@ def test_sendinvoice(node_factory, bitcoind):
|
||||
# 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})
|
||||
|
||||
# sendinvoice should work.
|
||||
out = l2.rpc.call('sendinvoice', {'offer': offer,
|
||||
'label': 'test sendinvoice 1'})
|
||||
print(out)
|
||||
|
||||
# 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,
|
||||
'label': 'test sendinvoice 2'})
|
||||
|
||||
# Now try a refund.
|
||||
offer = l2.rpc.call('offer', {'amount': '100msat',
|
||||
'description': 'simple test'})['bolt12']
|
||||
|
||||
inv = l1.rpc.call('fetchinvoice', {'offer': offer})
|
||||
l1.rpc.pay(inv['invoice'])
|
||||
|
||||
refund = l2.rpc.call('offer', {'amount': '100msat',
|
||||
'description': 'refund test',
|
||||
'refund_for': inv['invoice']})['bolt12']
|
||||
|
||||
l1.rpc.call('sendinvoice', {'offer': refund,
|
||||
'label': 'test sendinvoice refund'})
|
||||
|
||||
Reference in New Issue
Block a user