invoices: Add expiration timer system.

Fixes: #502

Changes behavior of waitinvoice API!!
This commit is contained in:
ZmnSCPxj 2018-01-23 01:02:10 +00:00 committed by Rusty Russell
parent 9af923b6d0
commit 59abc37258
5 changed files with 108 additions and 8 deletions

View File

@ -23,7 +23,7 @@ static const char *invoice_status_str(const struct invoice *inv)
{
if (inv->state == PAID)
return "paid";
if (time_now().ts.tv_sec > inv->expiry_time)
if (inv->state == EXPIRED)
return "expired";
return "unpaid";
}
@ -396,7 +396,7 @@ static void json_waitinvoice(struct command *cmd,
if (!i) {
command_fail(cmd, "Label not found");
return;
} else if (i->state == PAID) {
} else if (i->state == PAID || i->state == EXPIRED) {
tell_waiter(cmd, i);
return;
} else {
@ -410,7 +410,7 @@ static void json_waitinvoice(struct command *cmd,
static const struct json_command waitinvoice_command = {
"waitinvoice",
json_waitinvoice,
"Wait for an incoming payment matching the invoice with {label}"
"Wait for an incoming payment matching the invoice with {label}, or if the invoice expires"
};
AUTODATA(json_command, &waitinvoice_command);

View File

@ -11,6 +11,8 @@
#include <lightningd/log.h>
#include <sodium/randombytes.h>
#include <sqlite3.h>
#include <string.h>
#include <common/timeout.h>
#include <common/utils.h>
struct invoice_waiter {
@ -69,6 +71,8 @@ static bool wallet_stmt2invoice(sqlite3_stmt *stmt, struct invoice *inv)
}
list_head_init(&inv->waitone_waiters);
inv->expiration_timer = NULL;
return true;
}
@ -89,13 +93,96 @@ struct invoices *invoices_new(const tal_t *ctx,
return invs;
}
static void install_expiration_timer(struct invoices *invoices,
struct invoice *i);
static void trigger_expiration(struct invoice *i)
{
struct invoices *invoices = i->owner;
u64 now = time_now().ts.tv_sec;
sqlite3_stmt *stmt;
struct invoice_waiter *w;
assert(i->state == UNPAID);
/* Timer already triggered, destroy the timer object. */
i->expiration_timer = tal_free(i->expiration_timer);
/* There may be discrepancies between time_mono
* (used by the timer system) and time_now (used
* by the expiry time measurements). So check that
* time_now is reached. */
if (i->expiry_time <= now) {
const tal_t *tmpctx = tal_tmpctx(i);
/* Update in-memory and db. */
i->state = EXPIRED;
stmt = db_prepare(invoices->db,
"UPDATE invoices"
" SET state = ?"
" WHERE id = ?;");
sqlite3_bind_int(stmt, 1, EXPIRED);
sqlite3_bind_int64(stmt, 2, i->id);
db_exec_prepared(invoices->db, stmt);
/* Wake up all waiters. */
while ((w = list_pop(&i->waitone_waiters,
struct invoice_waiter,
list)) != NULL) {
tal_steal(tmpctx, w);
trigger_invoice_waiter(w, i);
}
tal_free(tmpctx);
} else
install_expiration_timer(invoices, i);
}
static void install_expiration_timer(struct invoices *invoices,
struct invoice *i)
{
struct timerel rel;
struct timeabs expiry;
struct timeabs now = time_now();
assert(i->state == UNPAID);
assert(!i->expiration_timer);
memset(&expiry, 0, sizeof(expiry));
expiry.ts.tv_sec = i->expiry_time;
/* now > expiry */
if (time_after(now, expiry))
expiry = now;
/* rel = expiry - now */
rel = time_between(expiry, now);
/* The oneshot is parented on the invoice. Thus if
* the invoice is deleted, the oneshot is destroyed
* also and this removes the timer. */
i->expiration_timer = new_reltimer(invoices->timers,
i,
rel,
&trigger_expiration, i);
}
bool invoices_load(struct invoices *invoices)
{
int count = 0;
u64 now = time_now().ts.tv_sec;
struct invoice *i;
sqlite3_stmt *stmt;
/* Update expirations. */
stmt = db_prepare(invoices->db,
"UPDATE invoices"
" SET state = ?"
" WHERE state = ?"
" AND expiry_time <= ?;");
sqlite3_bind_int(stmt, 1, EXPIRED);
sqlite3_bind_int(stmt, 2, UNPAID);
sqlite3_bind_int64(stmt, 3, now);
db_exec_prepared(invoices->db, stmt);
/* Load invoices from db. */
stmt = db_query(__func__, invoices->db,
"SELECT id, state, payment_key, payment_hash"
@ -109,17 +196,21 @@ bool invoices_load(struct invoices *invoices)
while (sqlite3_step(stmt) == SQLITE_ROW) {
i = tal(invoices, struct invoice);
i->owner = invoices;
if (!wallet_stmt2invoice(stmt, i)) {
log_broken(invoices->log, "Error deserializing invoice");
sqlite3_finalize(stmt);
return false;
}
if (i->state == UNPAID)
install_expiration_timer(invoices, i);
list_add_tail(&invoices->invlist, &i->list);
count++;
}
log_debug(invoices->log, "Loaded %d invoices from DB", count);
sqlite3_finalize(stmt);
return true;
}
@ -133,6 +224,7 @@ const struct invoice *invoices_create(struct invoices *invoices,
struct preimage r;
struct sha256 rhash;
u64 expiry_time;
u64 now = time_now().ts.tv_sec;
if (invoices_find_by_label(invoices, label)) {
if (taken(msatoshi))
@ -143,7 +235,7 @@ const struct invoice *invoices_create(struct invoices *invoices,
}
/* Compute expiration. */
expiry_time = time_now().ts.tv_sec + expiry;
expiry_time = now + expiry;
/* Generate random secret preimage and hash. */
randombytes_buf(r.r, sizeof(r.r));
sha256(&rhash, r.r, sizeof(r.r));
@ -177,6 +269,7 @@ const struct invoice *invoices_create(struct invoices *invoices,
/* Create and load in-memory structure. */
invoice = tal(invoices, struct invoice);
invoice->owner = invoices;
invoice->id = sqlite3_last_insert_rowid(invoices->db->sql);
invoice->state = UNPAID;
@ -186,10 +279,14 @@ const struct invoice *invoices_create(struct invoices *invoices,
memcpy(&invoice->rhash, &rhash, sizeof(invoice->rhash));
invoice->expiry_time = expiry_time;
list_head_init(&invoice->waitone_waiters);
invoice->expiration_timer = NULL;
/* Add to invoices object. */
list_add_tail(&invoices->invlist, &invoice->list);
/* Install expiration trigger. */
install_expiration_timer(invoices, invoice);
return invoice;
}
@ -314,6 +411,7 @@ void invoices_resolve(struct invoices *invoices,
invoice->pay_index = pay_index;
invoice->msatoshi_received = msatoshi_received;
invoice->paid_timestamp = paid_timestamp;
invoice->expiration_timer = tal_free(invoice->expiration_timer);
/* Tell all the waitany waiters about the new paid invoice. */
while ((w = list_pop(&invoices->waitany_waiters,
@ -408,8 +506,7 @@ void invoices_waitone(const tal_t *ctx,
void *cbarg)
{
struct invoice *invoice = (struct invoice*) cinvoice;
/* FIXME: Handle expired state. */
if (invoice->state == PAID) {
if (invoice->state == PAID || invoice->state == EXPIRED) {
cb(invoice, cbarg);
return;
}

View File

@ -152,7 +152,6 @@ void invoices_waitany(const tal_t *ctx,
* invoice.
* @cbarg - the callback data.
*
* FIXME: actually trigger on expired invoices.
*/
void invoices_waitone(const tal_t *ctx,
struct invoices *invoices,

View File

@ -7,6 +7,7 @@ WALLET_TEST_COMMON_OBJS := \
common/type_to_string.o \
common/memleak.o \
common/pseudorand.o \
common/timeout.o \
common/utils.o \
common/wireaddr.o \
wallet/invoices.o \

View File

@ -16,6 +16,7 @@
struct invoices;
struct lightningd;
struct oneshot;
struct pubkey;
struct timers;
@ -362,6 +363,7 @@ bool wallet_htlcs_reconnect(struct wallet *wallet,
enum invoice_status {
UNPAID,
PAID,
EXPIRED,
};
struct invoice {
@ -375,6 +377,8 @@ struct invoice {
struct list_head waitone_waiters;
/* Any expiration timer in effect */
struct oneshot *expiration_timer;
/* The owning invoices object. */
struct invoices *owner;
/* Publicly-useable fields. */
enum invoice_status state;
@ -527,7 +531,6 @@ void wallet_invoice_waitany(const tal_t *ctx,
* invoice.
* @cbarg - the callback data.
*
* FIXME: actually trigger on expired invoices.
*/
void wallet_invoice_waitone(const tal_t *ctx,
struct wallet *wallet,