JSONRPC: listpayments can list just a specific bolt11 or payment_hash.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2018-01-17 06:14:32 +10:30
parent 06c4f6ddca
commit 6e703ad977
4 changed files with 97 additions and 23 deletions

View File

@ -441,8 +441,49 @@ static void json_listpayments(struct command *cmd, const char *buffer,
{
const struct wallet_payment **payments;
struct json_result *response = new_json_result(cmd);
jsmntok_t *bolt11tok, *rhashtok;
struct sha256 *rhash = NULL;
payments = wallet_payment_list(cmd, cmd->ld->wallet);
if (!json_get_params(buffer, params,
"?bolt11", &bolt11tok,
"?payment_hash", &rhashtok,
NULL)) {
command_fail(cmd, "Invalid parameters");
return;
}
if (bolt11tok) {
struct bolt11 *b11;
char *b11str, *fail;
if (rhashtok) {
command_fail(cmd, "Can only specify one of"
" {bolt11} or {payment_hash}");
return;
}
b11str = tal_strndup(cmd, buffer + bolt11tok->start,
bolt11tok->end - bolt11tok->start);
b11 = bolt11_decode(cmd, b11str, NULL, &fail);
if (!b11) {
command_fail(cmd, "Invalid bolt11: %s", fail);
return;
}
rhash = &b11->payment_hash;
} else if (rhashtok) {
rhash = tal(cmd, struct sha256);
if (!hex_decode(buffer + rhashtok->start,
rhashtok->end - rhashtok->start,
rhash, sizeof(*rhash))) {
command_fail(cmd, "'%.*s' is not a valid sha256 hash",
(int)(rhashtok->end - rhashtok->start),
buffer + rhashtok->start);
return;
}
}
payments = wallet_payment_list(cmd, cmd->ld->wallet, rhash);
json_array_start(response, NULL);
for (int i=0; i<tal_count(payments); i++) {
@ -479,7 +520,7 @@ static void json_listpayments(struct command *cmd, const char *buffer,
static const struct json_command listpayments_command = {
"listpayments",
json_listpayments,
"Get a list of incoming and outgoing payments",
"Returns a list of payments with {payment_hash}, {destination}, {msatoshi}, {timestamp} and {status}"
"Get a list of outgoing payments",
"Returns a list of payments with {payment_hash}, {destination}, {msatoshi}, {timestamp} and {status} (and {payment_preimage} if {status} is 'complete')"
};
AUTODATA(json_command, &listpayments_command);

View File

@ -678,7 +678,7 @@ class LightningDTests(BaseLightningDTests):
assert p2['msatoshi_total'] == 10**6 * 1000
# This works.
l1.rpc.sendpay(to_json([routestep]), rhash)
preimage2 = l1.rpc.sendpay(to_json([routestep]), rhash)
assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == True
assert l2.rpc.listinvoice('testpayment2')[0]['pay_index'] == 1
assert l2.rpc.listinvoice('testpayment2')[0]['msatoshi_received'] == rs['msatoshi']
@ -694,7 +694,8 @@ class LightningDTests(BaseLightningDTests):
# Repeat will "succeed", but won't actually send anything (duplicate)
assert not l1.daemon.is_in_log('... succeeded')
l1.rpc.sendpay(to_json([routestep]), rhash)
preimage = l1.rpc.sendpay(to_json([routestep]), rhash)
assert preimage == preimage2
l1.daemon.wait_for_log('... succeeded')
assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == True
assert l2.rpc.listinvoice('testpayment2')[0]['msatoshi_received'] == rs['msatoshi']
@ -703,9 +704,16 @@ class LightningDTests(BaseLightningDTests):
rhash = l2.rpc.invoice(amt, 'testpayment3', 'desc')['payment_hash']
assert l2.rpc.listinvoice('testpayment3')[0]['complete'] == False
routestep = { 'msatoshi' : amt * 2, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'}
l1.rpc.sendpay(to_json([routestep]), rhash)
preimage3 = l1.rpc.sendpay(to_json([routestep]), rhash)
assert l2.rpc.listinvoice('testpayment3')[0]['complete'] == True
assert l2.rpc.listinvoice('testpayment3')[0]['msatoshi_received'] == amt*2
# Test listpayments
assert len(l1.rpc.listpayments()) == 2
assert len(l1.rpc.listpayments(None, l2.rpc.listinvoice('testpayment2')[0]['payment_hash'])) == 1
assert l1.rpc.listpayments(None, l2.rpc.listinvoice('testpayment2')[0]['payment_hash'])[0]['status'] == 'complete'
assert l1.rpc.listpayments(None, l2.rpc.listinvoice('testpayment2')[0]['payment_hash'])[0]['payment_preimage'] == preimage2['preimage']
assert l1.rpc.listpayments(None, l2.rpc.listinvoice('testpayment3')[0]['payment_hash'])[0]['status'] == 'complete'
assert l1.rpc.listpayments(None, l2.rpc.listinvoice('testpayment3')[0]['payment_hash'])[0]['payment_preimage'] == preimage3['preimage']
def test_sendpay_cant_afford(self):
l1,l2 = self.connect()
@ -744,7 +752,7 @@ class LightningDTests(BaseLightningDTests):
inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11']
before = int(time.time())
l1.rpc.pay(inv)
preimage = l1.rpc.pay(inv)
after = int(time.time())
invoice = l2.rpc.listinvoice('test_pay')[0]
assert invoice['complete'] == True
@ -764,11 +772,18 @@ class LightningDTests(BaseLightningDTests):
# Check payment of any-amount invoice.
for i in range(5):
label = "any{}".format(i)
inv = l2.rpc.invoice("any", label, 'description')['bolt11']
inv2 = l2.rpc.invoice("any", label, 'description')['bolt11']
# Must provide an amount!
self.assertRaises(ValueError, l1.rpc.pay, inv)
self.assertRaises(ValueError, l1.rpc.pay, inv, None)
l1.rpc.pay(inv, random.randint(1000, 999999))
self.assertRaises(ValueError, l1.rpc.pay, inv2)
self.assertRaises(ValueError, l1.rpc.pay, inv2, None)
l1.rpc.pay(inv2, random.randint(1000, 999999))
# Should see 6 completed payments
assert len(l1.rpc.listpayments()) == 6
# Test listpayments indexed by bolt11.
assert len(l1.rpc.listpayments(inv)) == 1
assert l1.rpc.listpayments(inv)[0]['payment_preimage'] == preimage['preimage']
def test_bad_opening(self):
# l1 asks for a too-long locktime
@ -1785,7 +1800,7 @@ class LightningDTests(BaseLightningDTests):
assert c[1]['short_channel_id'] == scid
assert c[0]['source'] == c[1]['destination']
assert c[1]['source'] == c[0]['destination']
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval")
def test_forward_pad_fees_and_cltv(self):
"""Test that we are allowed extra locktime delta, and fees"""
@ -2602,7 +2617,7 @@ class LightningDTests(BaseLightningDTests):
# FIXME: We should re-add pre-announced routes on startup!
self.wait_for_routes(l1, [chanid])
# A duplicate should succeed immediately (nop) and return correct preimage.
preimage = l1.rpc.pay(inv1['bolt11'])['preimage']
assert l1.rpc.dev_rhash(preimage)['rhash'] == inv1['payment_hash']
@ -2646,7 +2661,7 @@ class LightningDTests(BaseLightningDTests):
# Another attempt should also fail.
self.assertRaises(ValueError, l1.rpc.pay, inv1['bolt11'])
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
def test_payment_duplicate_uncommitted(self):
# We want to test two payments at the same time, before we send commit

View File

@ -1431,8 +1431,10 @@ void wallet_payment_set_status(struct wallet *wallet,
}
}
const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
struct wallet *wallet)
const struct wallet_payment **
wallet_payment_list(const tal_t *ctx,
struct wallet *wallet,
const struct sha256 *payment_hash)
{
const struct wallet_payment **payments;
sqlite3_stmt *stmt;
@ -1440,12 +1442,23 @@ const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
size_t i;
payments = tal_arr(ctx, const struct wallet_payment *, 0);
stmt = db_prepare(
wallet->db,
"SELECT id, status, destination, "
"msatoshi, payment_hash, timestamp, payment_preimage, "
"path_secrets "
"FROM payments;");
if (payment_hash) {
stmt = db_prepare(
wallet->db,
"SELECT id, status, destination, "
"msatoshi, payment_hash, timestamp, payment_preimage, "
"path_secrets "
"FROM payments "
"WHERE payment_hash = ?;");
sqlite3_bind_sha256(stmt, 1, payment_hash);
} else {
stmt = db_prepare(
wallet->db,
"SELECT id, status, destination, "
"msatoshi, payment_hash, timestamp, payment_preimage, "
"path_secrets "
"FROM payments;");
}
for (i = 0; sqlite3_step(stmt) == SQLITE_ROW; i++) {
tal_resize(&payments, i+1);
@ -1456,6 +1469,8 @@ const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
/* Now attach payments not yet in db. */
list_for_each(&wallet->unstored_payments, p, list) {
if (payment_hash && !structeq(&p->payment_hash, payment_hash))
continue;
tal_resize(&payments, i+1);
payments[i++] = p;
}

View File

@ -592,8 +592,11 @@ struct secret *wallet_payment_get_secrets(const tal_t *ctx,
/**
* wallet_payment_list - Retrieve a list of payments
*
* payment_hash: optional filter for only this payment hash.
*/
const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
struct wallet *wallet);
struct wallet *wallet,
const struct sha256 *payment_hash);
#endif /* WALLET_WALLET_H */