diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 01e1b34db..d540b8ff5 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -31,6 +31,8 @@ static void json_add_invoice(struct json_result *response, json_add_u64(response, "pay_index", inv->pay_index); json_add_u64(response, "msatoshi_received", inv->msatoshi_received); + json_add_u64(response, "paid_timestamp", + inv->paid_timestamp); } json_add_u64(response, "expiry_time", inv->expiry_time); json_object_end(response); diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index 9cc71dd88..0b9340db8 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -743,8 +743,13 @@ class LightningDTests(BaseLightningDTests): self.wait_for_routes(l1, [chanid]) inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11'] + before = int(time.time()) l1.rpc.pay(inv) - assert l2.rpc.listinvoice('test_pay')[0]['complete'] == True + after = int(time.time()) + invoice = l2.rpc.listinvoice('test_pay')[0] + assert invoice['complete'] == True + assert invoice['paid_timestamp'] >= before + assert invoice['paid_timestamp'] <= after # Repeat payments are NOPs (if valid): we can hand null. l1.rpc.pay(inv, None) diff --git a/wallet/db.c b/wallet/db.c index 93265d88e..d25e8ef15 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -177,6 +177,12 @@ char *dbmigrations[] = { "ALTER TABLE payments ADD COLUMN payment_preimage BLOB;", /* We need to keep the shared secrets to decode error returns. */ "ALTER TABLE payments ADD COLUMN path_secrets BLOB;", + /* Create time-of-payment of invoice, default already-paid + * invoices to current time. */ + "ALTER TABLE invoices ADD paid_timestamp INTEGER;", + "UPDATE invoices" + " SET paid_timestamp = strftime('%s', 'now')" + " WHERE state = 1;", NULL, }; diff --git a/wallet/invoices.c b/wallet/invoices.c index 51fcdccc4..86e2088a0 100644 --- a/wallet/invoices.c +++ b/wallet/invoices.c @@ -61,8 +61,10 @@ static bool 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) + if (inv->state == PAID) { inv->msatoshi_received = sqlite3_column_int64(stmt, 8); + inv->paid_timestamp = sqlite3_column_int64(stmt, 9); + } list_head_init(&inv->waitone_waiters); return true; @@ -94,7 +96,7 @@ bool invoices_load(struct invoices *invoices) stmt = db_query(__func__, invoices->db, "SELECT id, state, payment_key, payment_hash" " , label, msatoshi, expiry_time, pay_index" - " , msatoshi_received" + " , msatoshi_received, paid_timestamp" " FROM invoices;"); if (!stmt) { log_broken(invoices->log, "Could not load invoices"); @@ -148,8 +150,14 @@ const struct invoice *invoices_create(struct invoices *invoices, * that string for sql injections... */ stmt = db_prepare(invoices->db, "INSERT INTO invoices" - " (payment_hash, payment_key, state, msatoshi, label, expiry_time, pay_index, msatoshi_received)" - " VALUES (?, ?, ?, ?, ?, ?, NULL, NULL);"); + " ( payment_hash, payment_key, state" + " , msatoshi, label, expiry_time" + " , pay_index, msatoshi_received" + " , paid_timestamp)" + " VALUES ( ?, ?, ?" + " , ?, ?, ?" + " , NULL, NULL" + " , NULL);"); sqlite3_bind_blob(stmt, 1, &rhash, sizeof(rhash), SQLITE_TRANSIENT); sqlite3_bind_blob(stmt, 2, &r, sizeof(r), SQLITE_TRANSIENT); @@ -274,11 +282,12 @@ void invoices_resolve(struct invoices *invoices, struct invoice_waiter *w; struct invoice *invoice = (struct invoice *)cinvoice; s64 pay_index; + u64 paid_timestamp; const tal_t *tmpctx = tal_tmpctx(NULL); /* Assign a pay-index. */ pay_index = get_next_pay_index(invoices->db); - /* FIXME: Save time of payment. */ + paid_timestamp = time_now().ts.tv_sec; /* Update database. */ stmt = db_prepare(invoices->db, @@ -286,17 +295,20 @@ void invoices_resolve(struct invoices *invoices, " SET state=?" " , pay_index=?" " , msatoshi_received=?" + " , paid_timestamp=?" " WHERE id=?;"); sqlite3_bind_int(stmt, 1, PAID); sqlite3_bind_int64(stmt, 2, pay_index); sqlite3_bind_int64(stmt, 3, msatoshi_received); - sqlite3_bind_int64(stmt, 4, invoice->id); + sqlite3_bind_int64(stmt, 4, paid_timestamp); + sqlite3_bind_int64(stmt, 5, invoice->id); db_exec_prepared(invoices->db, stmt); /* Update in-memory structure. */ invoice->state = PAID; invoice->pay_index = pay_index; invoice->msatoshi_received = msatoshi_received; + invoice->paid_timestamp = paid_timestamp; /* Tell all the waitany waiters about the new paid invoice. */ while ((w = list_pop(&invoices->waitany_waiters, diff --git a/wallet/wallet.h b/wallet/wallet.h index dc8778847..17923e515 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -365,6 +365,8 @@ struct invoice { u64 *msatoshi; /* Set if state == PAID */ u64 msatoshi_received; + /* Set if state == PAID */ + u64 paid_timestamp; struct preimage r; u64 expiry_time; struct sha256 rhash;