Browse Source

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 <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 7 years ago
parent
commit
2f2fb0c2a1
  1. 2
      doc/lightning-listinvoice.7
  2. 2
      doc/lightning-listinvoice.7.txt
  3. 9
      lightningd/invoice.c
  4. 10
      lightningd/invoice.h
  5. 2
      lightningd/peer_htlcs.c
  6. 4
      tests/test_lightningd.py
  7. 3
      wallet/db.c
  8. 8
      wallet/wallet.c

2
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 <rusty@rustcorp\&.com\&.au> is mainly responsible\&.

2
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

9
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. */

10
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);

2
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:

4
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()

3
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,
};

8
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");

Loading…
Cancel
Save