Skip to content

Commit

Permalink
invoice: allow suffixes.
Browse files Browse the repository at this point in the history
Makes it much easier to set it to 6 hours, for example.

Fixes: ElementsProject#2551
Signed-off-by: Rusty Russell <[email protected]>
  • Loading branch information
rustyrussell authored and niftynei committed Apr 11, 2019
1 parent 1d6584e commit ba41238
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 5 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### 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

Expand Down
2 changes: 1 addition & 1 deletion doc/lightning-invoice.7
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The \fIlabel\fR must be a unique string or number (which is treated as a string,
.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\&.
.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
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
Expand Down
7 changes: 5 additions & 2 deletions doc/lightning-invoice.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
UTF-8, and cannot use '\u' JSON escape codes.

The 'expiry' 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 'expiry' is optionally the time the invoice is valid for; without
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 invoice (in order from most-preferred to least): note that these
Expand Down
54 changes: 53 additions & 1 deletion lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <bitcoin/address.h>
#include <bitcoin/base58.h>
#include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <common/amount.h>
Expand All @@ -14,6 +15,7 @@
#include <common/json_escaped.h>
#include <common/json_helpers.h>
#include <common/jsonrpc_errors.h>
#include <common/overflows.h>
#include <common/param.h>
#include <common/pseudorand.h>
#include <common/utils.h>
Expand Down Expand Up @@ -389,6 +391,56 @@ static struct command_result *param_msat_or_any(struct command *cmd,
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,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
Expand All @@ -415,7 +467,7 @@ static struct command_result *json_invoice(struct command *cmd,
p_req("msatoshi", param_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_u64, &expiry, 3600*24*7),
p_opt_def("expiry", param_time, &expiry, 3600*24*7),
p_opt("fallbacks", param_array, &fallbacks),
p_opt("preimage", param_tok, &preimagetok),
p_opt("exposeprivatechannels", param_bool, &exposeprivate),
Expand Down
31 changes: 31 additions & 0 deletions tests/test_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,37 @@ def test_invoice_expiry(node_factory, executor):
# all invoices are expired and should be deleted
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")
def test_waitinvoice(node_factory, executor):
Expand Down

0 comments on commit ba41238

Please sign in to comment.