Browse Source

invoice: allow suffixes.

Makes it much easier to set it to 6 hours, for example.

Fixes: #2551
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
pr-2587
Rusty Russell 6 years ago
committed by neil saitug
parent
commit
ba41238df9
  1. 2
      CHANGELOG.md
  2. 2
      doc/lightning-invoice.7
  3. 7
      doc/lightning-invoice.7.txt
  4. 54
      lightningd/invoice.c
  5. 31
      tests/test_invoices.py

2
CHANGELOG.md

@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- JSON API: `invoice` expiry defaults to 7 days. - JSON API: `invoice` expiry defaults to 7 days, and can have s/m/h/d/w suffixes.
### Deprecated ### Deprecated

2
doc/lightning-invoice.7

@ -42,7 +42,7 @@ The \fIlabel\fR must be a unique string or number (which is treated as a string,
.sp .sp
The \fIdescription\fR is a short description of purpose of payment, e\&.g\&. \fI1 cup of coffee\fR\&. This value is encoded into the BOLT11 invoice and is viewable by any node you send this invoice to\&. It must be UTF\-8, and cannot use \fI\eu\fR JSON escape codes\&. The \fIdescription\fR is a short description of purpose of payment, e\&.g\&. \fI1 cup of coffee\fR\&. This value is encoded into the BOLT11 invoice and is viewable by any node you send this invoice to\&. It must be UTF\-8, and cannot use \fI\eu\fR JSON escape codes\&.
.sp .sp
The \fIexpiry\fR is optionally the number of seconds the invoice is valid for\&. If no value is provided the default of 604800 (1 week) is used\&. The \fIexpiry\fR is optionally the time the invoice is valid for; without a suffix it is interpreted as seconds, otherwise suffixes \fIs\fR, \fIm\fR, \fIh\fR, \fId\fR, \fIw\fR indicate seconds, minutes, hours, days and weeks respectively\&. If no value is provided the default of 604800 (1w) is used\&.
.sp .sp
The \fIfallbacks\fR array is one or more fallback addresses to include in the invoice (in order from most\-preferred to least): note that these arrays are not currently tracked to fulfill the invoice\&. The \fIfallbacks\fR array is one or more fallback addresses to include in the invoice (in order from most\-preferred to least): note that these arrays are not currently tracked to fulfill the invoice\&.
.sp .sp

7
doc/lightning-invoice.7.txt

@ -34,8 +34,11 @@ e.g. '1 cup of coffee'. This value is encoded into the BOLT11 invoice
and is viewable by any node you send this invoice to. It must be and is viewable by any node you send this invoice to. It must be
UTF-8, and cannot use '\u' JSON escape codes. UTF-8, and cannot use '\u' JSON escape codes.
The 'expiry' is optionally the number of seconds the invoice is valid for. The 'expiry' is optionally the time the invoice is valid for; without
If no value is provided the default of 604800 (1 week) is used. a suffix it is interpreted as seconds, otherwise suffixes 's', 'm',
'h', 'd', 'w' indicate seconds, minutes, hours, days and weeks
respectively. If no value is provided the default of 604800 (1w)
is used.
The 'fallbacks' array is one or more fallback addresses to include in The 'fallbacks' array is one or more fallback addresses to include in
the invoice (in order from most-preferred to least): note that these the invoice (in order from most-preferred to least): note that these

54
lightningd/invoice.c

@ -5,6 +5,7 @@
#include <bitcoin/address.h> #include <bitcoin/address.h>
#include <bitcoin/base58.h> #include <bitcoin/base58.h>
#include <bitcoin/script.h> #include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <ccan/str/hex/hex.h> #include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <common/amount.h> #include <common/amount.h>
@ -14,6 +15,7 @@
#include <common/json_escaped.h> #include <common/json_escaped.h>
#include <common/json_helpers.h> #include <common/json_helpers.h>
#include <common/jsonrpc_errors.h> #include <common/jsonrpc_errors.h>
#include <common/overflows.h>
#include <common/param.h> #include <common/param.h>
#include <common/pseudorand.h> #include <common/pseudorand.h>
#include <common/utils.h> #include <common/utils.h>
@ -389,6 +391,56 @@ static struct command_result *param_msat_or_any(struct command *cmd,
buffer + tok->start); buffer + tok->start);
} }
/* 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(cmd, JSONRPC2_INVALID_PARAMS,
"'%s' string '%.*s' is too large",
name, tok->end - tok->start,
buffer + tok->start);
}
**secs *= mul;
return NULL;
}
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"'%s' should be a number with optional {s,m,h,d,w} suffix, not '%.*s'",
name, tok->end - tok->start, buffer + tok->start);
}
static struct command_result *json_invoice(struct command *cmd, static struct command_result *json_invoice(struct command *cmd,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNNEEDED, const jsmntok_t *obj UNNEEDED,
@ -415,7 +467,7 @@ static struct command_result *json_invoice(struct command *cmd,
p_req("msatoshi", param_msat_or_any, &msatoshi_val), p_req("msatoshi", param_msat_or_any, &msatoshi_val),
p_req("label", param_label, &info->label), p_req("label", param_label, &info->label),
p_req("description", param_escaped_string, &desc_val), p_req("description", param_escaped_string, &desc_val),
p_opt_def("expiry", param_u64, &expiry, 3600*24*7), p_opt_def("expiry", param_time, &expiry, 3600*24*7),
p_opt("fallbacks", param_array, &fallbacks), p_opt("fallbacks", param_array, &fallbacks),
p_opt("preimage", param_tok, &preimagetok), p_opt("preimage", param_tok, &preimagetok),
p_opt("exposeprivatechannels", param_bool, &exposeprivate), p_opt("exposeprivatechannels", param_bool, &exposeprivate),

31
tests/test_invoices.py

@ -297,6 +297,37 @@ def test_invoice_expiry(node_factory, executor):
# all invoices are expired and should be deleted # all invoices are expired and should be deleted
assert len(l2.rpc.listinvoices()['invoices']) == 0 assert len(l2.rpc.listinvoices()['invoices']) == 0
# Test expiry suffixes.
start = int(time.time())
inv = l2.rpc.invoice(msatoshi=123000, label='inv_s', description='description', expiry='1s')['bolt11']
end = int(time.time())
expiry = only_one(l2.rpc.listinvoices('inv_s')['invoices'])['expires_at']
assert expiry >= start + 1 and expiry <= end + 1
start = int(time.time())
inv = l2.rpc.invoice(msatoshi=123000, label='inv_m', description='description', expiry='1m')['bolt11']
end = int(time.time())
expiry = only_one(l2.rpc.listinvoices('inv_m')['invoices'])['expires_at']
assert expiry >= start + 60 and expiry <= end + 60
start = int(time.time())
inv = l2.rpc.invoice(msatoshi=123000, label='inv_h', description='description', expiry='1h')['bolt11']
end = int(time.time())
expiry = only_one(l2.rpc.listinvoices('inv_h')['invoices'])['expires_at']
assert expiry >= start + 3600 and expiry <= end + 3600
start = int(time.time())
inv = l2.rpc.invoice(msatoshi=123000, label='inv_d', description='description', expiry='1d')['bolt11']
end = int(time.time())
expiry = only_one(l2.rpc.listinvoices('inv_d')['invoices'])['expires_at']
assert expiry >= start + 24 * 3600 and expiry <= end + 24 * 3600
start = int(time.time())
inv = l2.rpc.invoice(msatoshi=123000, label='inv_w', description='description', expiry='1w')['bolt11']
end = int(time.time())
expiry = only_one(l2.rpc.listinvoices('inv_w')['invoices'])['expires_at']
assert expiry >= start + 7 * 24 * 3600 and expiry <= end + 7 * 24 * 3600
@unittest.skipIf(not DEVELOPER, "Too slow without --dev-bitcoind-poll") @unittest.skipIf(not DEVELOPER, "Too slow without --dev-bitcoind-poll")
def test_waitinvoice(node_factory, executor): def test_waitinvoice(node_factory, executor):

Loading…
Cancel
Save