From 2f2fb0c2a1c337095c3db956c0326dfcc6a7a788 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 13 Jan 2018 21:49:33 +1030 Subject: [PATCH] invoice: add msatoshi_received field. Paid invoices need to know how much was actually paid: both for the case where no 'msatoshi' amount was specified, and for the normal case, where clients are permitted to overpay in order to help them disguise their payments. While we migrate the db, we leave this field as 0 for old paid invoices. This is unhelpful for accounting, but at least clearly indicates what happened if we find this in the wild. Signed-off-by: Rusty Russell --- doc/lightning-listinvoice.7 | 2 +- doc/lightning-listinvoice.7.txt | 2 +- lightningd/invoice.c | 9 +++++++-- lightningd/invoice.h | 10 +++++++++- lightningd/peer_htlcs.c | 2 +- tests/test_lightningd.py | 4 ++++ wallet/db.c | 3 +++ wallet/wallet.c | 8 ++++++-- 8 files changed, 32 insertions(+), 8 deletions(-) diff --git a/doc/lightning-listinvoice.7 b/doc/lightning-listinvoice.7 index 6b058c0fb..135849752 100644 --- a/doc/lightning-listinvoice.7 +++ b/doc/lightning-listinvoice.7 @@ -37,7 +37,7 @@ lightning-listinvoice \- Protocol for querying invoice status The \fBlistinvoice\fR RPC command gets the status of a specific invoice, if it exists, or the status of all invoices if given no argument\&. .SH "RETURN VALUE" .sp -On success, an array \fIinvoices\fR of objects containing \fIlabel\fR, \fIpayment_hash, \*(Aqmsatoshi\fR (if not "any"), \fIcomplete\fR, \fIpay_index\fR (if paid) and \fIexpiry_time\fR} will be returned\&. \fIcomplete\fR is a boolean, and \fIexpiry_time\fR is the number of seconds since UNIX epoch\&. +On success, an array \fIinvoices\fR of objects is returned\&. Each object contains \fIlabel\fR, \fIpayment_hash, \*(Aqcomplete\fR (a boolean), and \fIexpiry_time\fR (a UNIX timestamp)\&. If the \fImsatoshi\fR argument to lightning\-invoice(7) was not "any", there will be an \fImsatoshi\fR field\&. If the invoice has been paid, there will be a \fIpay_index\fR field and an \fImsatoshi_received\fR field (which may be slightly greater than \fImsatoshi\fR as some overpaying is permitted to allow clients to obscure payment paths)\&. .SH "AUTHOR" .sp Rusty Russell is mainly responsible\&. diff --git a/doc/lightning-listinvoice.7.txt b/doc/lightning-listinvoice.7.txt index 5956d72f6..9f5c766e3 100644 --- a/doc/lightning-listinvoice.7.txt +++ b/doc/lightning-listinvoice.7.txt @@ -17,7 +17,7 @@ it exists, or the status of all invoices if given no argument. RETURN VALUE ------------ -On success, an array 'invoices' of objects is returned. Each object contains 'label', 'payment_hash, 'complete' (a boolean), and 'expiry_time' (a UNIX timestamp). If the 'msatoshi' argument to lightning-invoice(7) was not "any", there will be an 'msatoshi' field. If the invoice has been paid, there will be a 'pay_index' field. +On success, an array 'invoices' of objects is returned. Each object contains 'label', 'payment_hash, 'complete' (a boolean), and 'expiry_time' (a UNIX timestamp). If the 'msatoshi' argument to lightning-invoice(7) was not "any", there will be an 'msatoshi' field. If the invoice has been paid, there will be a 'pay_index' field and an 'msatoshi_received' field (which may be slightly greater than 'msatoshi' as some overpaying is permitted to allow clients to obscure payment paths). //FIXME:Enumerate errors diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 8497e037b..dbf7b5d7c 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -83,8 +83,11 @@ static void json_add_invoice(struct json_result *response, if (inv->msatoshi) json_add_u64(response, "msatoshi", *inv->msatoshi); json_add_bool(response, "complete", inv->state == PAID); - if (inv->state == PAID) + if (inv->state == PAID) { json_add_u64(response, "pay_index", inv->pay_index); + json_add_u64(response, "msatoshi_received", + inv->msatoshi_received); + } json_add_u64(response, "expiry_time", inv->expiry_time); json_object_end(response); } @@ -101,12 +104,14 @@ static void tell_waiter_deleted(struct command *cmd, const struct invoice *paid) command_fail(cmd, "invoice deleted during wait"); } -void resolve_invoice(struct lightningd *ld, struct invoice *invoice) +void resolve_invoice(struct lightningd *ld, struct invoice *invoice, + u64 msatoshi_received) { struct invoice_waiter *w; struct invoices *invs = ld->invoices; invoice->state = PAID; + invoice->msatoshi_received = msatoshi_received; /* wallet_invoice_save updates pay_index member, * which tell_waiter needs. */ diff --git a/lightningd/invoice.h b/lightningd/invoice.h index 2695f3608..8206fe634 100644 --- a/lightningd/invoice.h +++ b/lightningd/invoice.h @@ -17,15 +17,22 @@ enum invoice_status { }; struct invoice { + /* List off ld->invoices->invlist */ struct list_node list; + /* Database ID */ u64 id; enum invoice_status state; const char *label; + /* NULL if they specified "any" */ u64 *msatoshi; + /* Set if state == PAID */ + u64 msatoshi_received; struct preimage r; u64 expiry_time; struct sha256 rhash; + /* Non-zero if state == PAID */ u64 pay_index; + /* Any JSON waitinvoice calls waiting for this to be paid. */ struct list_head waitone_waiters; }; @@ -35,7 +42,8 @@ struct invoice { void invoice_add(struct invoices *invs, struct invoice *inv); -void resolve_invoice(struct lightningd *ld, struct invoice *invoice); +void resolve_invoice(struct lightningd *ld, struct invoice *invoice, + u64 msatoshi_received); struct invoice *find_unpaid(struct invoices *i, const struct sha256 *rhash); diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index a68709e75..998436bcd 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -297,7 +297,7 @@ static void handle_localpay(struct htlc_in *hin, log_debug(ld->log, "%s: Actual amount %"PRIu64"msat, HTLC expiry %u", invoice->label, hin->msatoshi, cltv_expiry); fulfill_htlc(hin, &invoice->r); - resolve_invoice(ld, invoice); + resolve_invoice(ld, invoice, hin->msatoshi); return; fail: diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index b4309aa91..dc748eb40 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -678,6 +678,8 @@ class LightningDTests(BaseLightningDTests): # This works. 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'] # Balances should reflect it. time.sleep(1) @@ -693,6 +695,7 @@ class LightningDTests(BaseLightningDTests): l1.rpc.sendpay(to_json([routestep]), rhash) l1.daemon.wait_for_log('... succeeded') assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == True + assert l2.rpc.listinvoice('testpayment2')[0]['msatoshi_received'] == rs['msatoshi'] # Overpaying by "only" a factor of 2 succeeds. rhash = l2.rpc.invoice(amt, 'testpayment3', 'desc')['payment_hash'] @@ -700,6 +703,7 @@ class LightningDTests(BaseLightningDTests): routestep = { 'msatoshi' : amt * 2, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} 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 def test_sendpay_cant_afford(self): l1,l2 = self.connect() diff --git a/wallet/db.c b/wallet/db.c index 887dc4954..d92d29a8b 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -153,6 +153,9 @@ char *dbmigrations[] = { "ALTER TABLE outputs ADD COLUMN channel_id INTEGER;", "ALTER TABLE outputs ADD COLUMN peer_id BLOB;", "ALTER TABLE outputs ADD COLUMN commitment_point BLOB;", + "ALTER TABLE invoices ADD COLUMN msatoshi_received INTEGER;", + /* Normally impossible, so at least we'll know if databases are ancient. */ + "UPDATE invoices SET msatoshi_received=0 WHERE state=1;", NULL, }; diff --git a/wallet/wallet.c b/wallet/wallet.c index 0e34ef5ce..b6d298970 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1171,6 +1171,8 @@ static void wallet_stmt2invoice(sqlite3_stmt *stmt, struct invoice *inv) /* Correctly 0 if pay_index is NULL. */ inv->pay_index = sqlite3_column_int64(stmt, 7); + if (inv->state == PAID) + inv->msatoshi_received = sqlite3_column_int64(stmt, 8); list_head_init(&inv->waitone_waiters); } @@ -1185,7 +1187,8 @@ struct invoice *wallet_invoice_nextpaid(const tal_t *ctx, /* Generate query. */ stmt = db_prepare(wallet->db, "SELECT id, state, payment_key, payment_hash," - " label, msatoshi, expiry_time, pay_index " + " label, msatoshi, expiry_time, pay_index," + " msatoshi_received " " FROM invoices" " WHERE pay_index NOT NULL" " AND pay_index > ?" @@ -1302,7 +1305,8 @@ bool wallet_invoices_load(struct wallet *wallet, struct invoices *invs) int count = 0; sqlite3_stmt *stmt = db_query(__func__, wallet->db, "SELECT id, state, payment_key, payment_hash, " - "label, msatoshi, expiry_time, pay_index " + "label, msatoshi, expiry_time, pay_index, " + "msatoshi_received " "FROM invoices;"); if (!stmt) { log_broken(wallet->log, "Could not load invoices");