Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout paameter to waitanyinvoice. #3449

Merged
merged 2 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/jsonrpc_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
#define INVOICE_LABEL_ALREADY_EXISTS 900
#define INVOICE_PREIMAGE_ALREADY_EXISTS 901
#define INVOICE_HINTS_GAVE_NO_ROUTES 902
#define INVOICE_WAIT_TIMED_OUT 904

#endif /* LIGHTNING_COMMON_JSONRPC_ERRORS_H */
8 changes: 6 additions & 2 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -988,14 +988,18 @@ def stop(self):
"""
return self.call("stop")

def waitanyinvoice(self, lastpay_index=None):
def waitanyinvoice(self, lastpay_index=None, timeout=None, **kwargs):
"""
Wait for the next invoice to be paid, after {lastpay_index}
(if supplied)
Fail after {timeout} seconds has passed without an invoice
being paid.
"""
payload = {
"lastpay_index": lastpay_index
"lastpay_index": lastpay_index,
"timeout": timeout
}
payload.update({k: v for k, v in kwargs.items()})
return self.call("waitanyinvoice", payload)

def waitblockheight(self, blockheight, timeout=None):
Expand Down
20 changes: 19 additions & 1 deletion doc/lightning-waitanyinvoice.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion doc/lightning-waitanyinvoice.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lightning-waitanyinvoice -- Command for waiting for payments
SYNOPSIS
--------

**waitanyinvoice** \[*lastpay\_index*\]
**waitanyinvoice** \[*lastpay\_index*\] \[*timeout*\]

DESCRIPTION
-----------
Expand All @@ -22,12 +22,25 @@ invoice when it gets paid. The first valid *pay\_index* is 1; specifying
*lastpay\_index* of 0 equivalent to not specifying a *lastpay\_index*.
Negative *lastpay\_index* is invalid.

If *timeout* is specified, wait at most that number of seconds, which
must be an integer.
If the specified *timeout* is reached, this command will return with an
error.
You can specify this to 0 so that **waitanyinvoice** will return
immediately with an error if no pending invoice is available yet.
If unspecified, this command will wait indefinitely.

RETURN VALUE
------------

On success, an invoice description will be returned as per
lightning-listinvoice(7): *complete* will always be *true*.

Possible errors are:

* 904.
The *timeout* was reached without an invoice being paid.

AUTHOR
------

Expand Down
22 changes: 21 additions & 1 deletion lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <common/overflows.h>
#include <common/param.h>
#include <common/pseudorand.h>
#include <common/timeout.h>
#include <common/utils.h>
#include <errno.h>
#include <gossipd/gen_gossip_wire.h>
Expand Down Expand Up @@ -102,6 +103,12 @@ static void wait_on_invoice(const struct invoice *invoice, void *cmd)
else
tell_waiter_deleted((struct command *) cmd);
}
static void wait_timed_out(struct command *cmd)
{
was_pending(command_fail(cmd, INVOICE_WAIT_TIMED_OUT,
"Timed out while waiting "
"for invoice to be paid"));
}

/* We derive invoice secret using 1-way function from payment_preimage
* (just a different one from the payment_hash!) */
Expand Down Expand Up @@ -1129,13 +1136,25 @@ static struct command_result *json_waitanyinvoice(struct command *cmd,
const jsmntok_t *params)
{
u64 *pay_index;
u64 *timeout;
struct wallet *wallet = cmd->ld->wallet;

if (!param(cmd, buffer, params,
p_opt_def("lastpay_index", param_u64, &pay_index, 0),
p_opt("timeout", &param_u64, &timeout),
NULL))
return command_param_failed();

/*~ We allocate the timeout and the wallet-waitanyinvoice
* in the cmd context, so whichever one manages to complete
* the command first (and destroy the cmd context)
* auto-cancels the other, is not tal amazing?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is 💯

*/
if (timeout)
(void) new_reltimer(cmd->ld->timers, cmd,
time_from_sec(*timeout),
&wait_timed_out, cmd);

/* Set command as pending. We do not know if
* wallet_invoice_waitany will return immediately
* or not, so indicating pending is safest. */
Expand All @@ -1155,7 +1174,8 @@ static const struct json_command waitanyinvoice_command = {
"waitanyinvoice",
"payment",
json_waitanyinvoice,
"Wait for the next invoice to be paid, after {lastpay_index} (if supplied)"
"Wait for the next invoice to be paid, after {lastpay_index} (if supplied). "
"If {timeout} seconds is reached while waiting, fail with an error."
};
AUTODATA(json_command, &waitanyinvoice_command);

Expand Down
13 changes: 13 additions & 0 deletions tests/test_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ def test_waitanyinvoice(node_factory, executor):
inv1 = l2.rpc.invoice(1000, 'inv1', 'inv1')
inv2 = l2.rpc.invoice(1000, 'inv2', 'inv2')
inv3 = l2.rpc.invoice(1000, 'inv3', 'inv3')
inv4 = l2.rpc.invoice(1000, 'inv4', 'inv4')

# Attempt to wait for the first invoice
f = executor.submit(l2.rpc.waitanyinvoice)
Expand Down Expand Up @@ -472,6 +473,18 @@ def test_waitanyinvoice(node_factory, executor):
l1.rpc.pay(inv3['bolt11'])
r = f.result(timeout=5)
assert r['label'] == 'inv3'
pay_index = r['pay_index']

# If timeout is 0 and a paid invoice is not yet
# available, it should fail immediately.
with pytest.raises(RpcError):
l2.rpc.waitanyinvoice(pay_index, 0)

# If timeout is 0 but a paid invoice is available
# anyway, it should return successfully immediately.
l1.rpc.pay(inv4['bolt11'])
r = executor.submit(l2.rpc.waitanyinvoice, pay_index, 0).result(timeout=5)
assert r['label'] == 'inv4'

with pytest.raises(RpcError):
l2.rpc.waitanyinvoice('non-number')
Expand Down