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

json-rpc: Add support for querying by hash or bolt11 to listinvoices #4312

Merged
merged 3 commits into from
Jan 6, 2021
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
13 changes: 9 additions & 4 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,17 @@ def listtransactions(self):
"""
return self.call("listtransactions")

def listinvoices(self, label=None):
"""
Show invoice {label} (or all, if no {label)).
def listinvoices(self, label=None, payment_hash=None, invstring=None):
"""Query invoices

Show invoice matching {label} {payment_hash} or {invstring} (or
all, if no filters are present).

"""
payload = {
"label": label
"label": label,
"payment_hash": payment_hash,
"invstring": invstring,
}
return self.call("listinvoices", payload)

Expand Down
10 changes: 8 additions & 2 deletions doc/lightning-listinvoices.7

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

7 changes: 6 additions & 1 deletion doc/lightning-listinvoices.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ lightning-listinvoices -- Command for querying invoice status
SYNOPSIS
--------

**listinvoices** \[*label*\]
**listinvoices** \[*label*\] \[*invstring*\] \[*payment_hash*\]

DESCRIPTION
-----------

The **listinvoices** RPC command gets the status of a specific invoice,
if it exists, or the status of all invoices if given no argument.

A specific invoice can be queried by providing either the `label`
provided when creating the invoice, the `invstring` string representing
the invoice, or the `payment_hash` of the invoice. Only one of the
query parameters can be used at once.

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

Expand Down
61 changes: 48 additions & 13 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1119,29 +1119,41 @@ AUTODATA(json_command, &invoice_command);

static void json_add_invoices(struct json_stream *response,
struct wallet *wallet,
const struct json_escape *label)
const struct json_escape *label,
const struct sha256 *payment_hash)
{
struct invoice_iterator it;
const struct invoice_details *details;
struct invoice invoice;

/* Don't iterate entire db if we're just after one. */
if (label) {
struct invoice invoice;
if (wallet_invoice_find_by_label(wallet, &invoice, label)) {
details = wallet_invoice_details(response, wallet, invoice);
details =
wallet_invoice_details(response, wallet, invoice);
json_object_start(response, NULL);
json_add_invoice(response, details);
json_object_end(response);
}
return;
}
} else if (payment_hash != NULL) {
if (wallet_invoice_find_by_rhash(wallet, &invoice,
payment_hash)) {
json_object_start(response, NULL);
json_add_invoice(
response,
wallet_invoice_details(response, wallet, invoice));
json_object_end(response);
}

memset(&it, 0, sizeof(it));
while (wallet_invoice_iterate(wallet, &it)) {
details = wallet_invoice_iterator_deref(response, wallet, &it);
json_object_start(response, NULL);
json_add_invoice(response, details);
json_object_end(response);
} else {
memset(&it, 0, sizeof(it));
while (wallet_invoice_iterate(wallet, &it)) {
details = wallet_invoice_iterator_deref(response,
wallet, &it);
json_object_start(response, NULL);
json_add_invoice(response, details);
json_object_end(response);
}
}
}

Expand All @@ -1153,13 +1165,35 @@ static struct command_result *json_listinvoices(struct command *cmd,
struct json_escape *label;
struct json_stream *response;
struct wallet *wallet = cmd->ld->wallet;
const char *invstring;
struct sha256 *payment_hash;
char *fail;
struct bolt11 *b11;

if (!param(cmd, buffer, params,
p_opt("label", param_label, &label),
p_opt("invstring", param_string, &invstring),
p_opt("payment_hash", param_sha256, &payment_hash),
NULL))
return command_param_failed();

if ((label && invstring) || (label && payment_hash) ||
(invstring && payment_hash)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Can only specify one of"
" {label}, {invstring} or {payment_hash}");
}

/* Extract the payment_hash from the invoice. */
if (invstring != NULL) {
b11 = bolt11_decode(cmd, invstring, cmd->ld->our_features, NULL,
NULL, &fail);
payment_hash = &b11->payment_hash;
}

response = json_stream_success(cmd);
json_array_start(response, "invoices");
json_add_invoices(response, wallet, label);
json_add_invoices(response, wallet, label, payment_hash);
json_array_end(response);
return command_success(cmd, response);
}
Expand All @@ -1168,7 +1202,8 @@ static const struct json_command listinvoices_command = {
"listinvoices",
"payment",
json_listinvoices,
"Show invoice {label} (or all, if no {label})"
"Show invoice matching {label}, {invstring} or {payment_hash} (or all, if "
"no query parameter specified)"
};
AUTODATA(json_command, &listinvoices_command);

Expand Down
5 changes: 5 additions & 0 deletions lightningd/test/run-invoice-select-inchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ struct command_result *param_number(struct command *cmd UNNEEDED, const char *na
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
unsigned int **num UNNEEDED)
{ fprintf(stderr, "param_number called!\n"); abort(); }
/* Generated stub for param_sha256 */
struct command_result *param_sha256(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct sha256 **hash UNNEEDED)
{ fprintf(stderr, "param_sha256 called!\n"); abort(); }
/* Generated stub for param_short_channel_id */
struct command_result *param_short_channel_id(struct command *cmd UNNEEDED,
const char *name UNNEEDED,
Expand Down
47 changes: 47 additions & 0 deletions tests/test_invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,50 @@ def test_amountless_invoice(node_factory):
assert(i[0]['msatoshi_received'] == 1337)
assert(i[0]['amount_received_msat'] == Millisatoshi(1337))
assert(i[0]['status'] == 'paid')


def test_listinvoices_filter(node_factory):
""" Test the optional query arguments to listinvoices
"""

l1 = node_factory.get_node()

invoices = [l1.rpc.invoice(42, 'label{}'.format(i), 'desc') for i in range(10)]

def match(node, query, invoice):
r1 = l1.rpc.listinvoices(**query)['invoices']
assert len(r1) == 1
assert r1[0]['payment_hash'] == inv['payment_hash']
assert r1[0]['bolt11'] == inv['bolt11']

for i, inv in enumerate(invoices):
match(l1, {'label': "label{}".format(i)}, inv)
match(l1, {'payment_hash': inv['payment_hash']}, inv)
match(l1, {'invstring': inv['bolt11']}, inv)

# Now test for failures

inv = invoices[0]
queries = [
{"payment_hash": inv['payment_hash'], "label": "label0"},
{"invstring": inv['bolt11'], "label": "label0"},
{"payment_hash": inv['payment_hash'], "label": "label0"},
]

for q in queries:
with pytest.raises(
RpcError,
match=r'Can only specify one of {label}, {invstring} or {payment_hash}'
):
l1.rpc.listinvoices(**q)

# Test querying for non-existent invoices
queries = [
{'label': 'doesnt exist'},
{'payment_hash': 'AA' * 32},
{'invstring': 'lnbcrt420p1p0lfrl6pp5w4zsagnfqu08s93rd44z93s8tt920hd9jec2yph969wluwkzrwpqdq8v3jhxccxqyjw5qcqp9sp52kw0kp75f6v2jusd8nsg2nfmdr82pqj0gf3jc8tqp7a2j48rzweq9qy9qsqtlu8eslmd4yxqrtrz75v8vmqrwknnk64sm79cj4asxhgndnj22r3g2a6axdvfdkhw966zw63cy3uzzn5hxad9ja8amqpp3wputl3ffcpallm2g'},
]

for q in queries:
r = l1.rpc.listinvoices(**q)
assert len(r['invoices']) == 0