ZmnSCPxj
7 years ago
committed by
Christian Decker
12 changed files with 827 additions and 462 deletions
@ -0,0 +1,402 @@ |
|||||
|
#include "db.h" |
||||
|
#include "invoices.h" |
||||
|
#include "wallet.h" |
||||
|
#include <assert.h> |
||||
|
#include <ccan/list/list.h> |
||||
|
#include <ccan/structeq/structeq.h> |
||||
|
#include <ccan/tal/str/str.h> |
||||
|
#include <ccan/time/time.h> |
||||
|
#include <lightningd/invoice.h> |
||||
|
#include <lightningd/log.h> |
||||
|
#include <sodium/randombytes.h> |
||||
|
#include <sqlite3.h> |
||||
|
#include <common/utils.h> |
||||
|
|
||||
|
struct invoice_waiter { |
||||
|
bool triggered; |
||||
|
struct list_node list; |
||||
|
void (*cb)(const struct invoice *, void*); |
||||
|
void *cbarg; |
||||
|
}; |
||||
|
|
||||
|
struct invoices { |
||||
|
/* The database connection to use. */ |
||||
|
struct db *db; |
||||
|
/* The log to report to. */ |
||||
|
struct log *log; |
||||
|
/* The invoice list. */ |
||||
|
struct list_head invlist; |
||||
|
/* Waiters waiting for any new invoice to be paid. */ |
||||
|
struct list_head waitany_waiters; |
||||
|
}; |
||||
|
|
||||
|
static void trigger_invoice_waiter(struct invoice_waiter *w, |
||||
|
struct invoice *invoice) |
||||
|
{ |
||||
|
w->triggered = true; |
||||
|
w->cb(invoice, w->cbarg); |
||||
|
} |
||||
|
|
||||
|
static bool wallet_stmt2invoice(sqlite3_stmt *stmt, struct invoice *inv) |
||||
|
{ |
||||
|
inv->id = sqlite3_column_int64(stmt, 0); |
||||
|
inv->state = sqlite3_column_int(stmt, 1); |
||||
|
|
||||
|
assert(sqlite3_column_bytes(stmt, 2) == sizeof(struct preimage)); |
||||
|
memcpy(&inv->r, sqlite3_column_blob(stmt, 2), sqlite3_column_bytes(stmt, 2)); |
||||
|
|
||||
|
assert(sqlite3_column_bytes(stmt, 3) == sizeof(struct sha256)); |
||||
|
memcpy(&inv->rhash, sqlite3_column_blob(stmt, 3), sqlite3_column_bytes(stmt, 3)); |
||||
|
|
||||
|
inv->label = tal_strndup(inv, sqlite3_column_blob(stmt, 4), sqlite3_column_bytes(stmt, 4)); |
||||
|
|
||||
|
if (sqlite3_column_type(stmt, 5) != SQLITE_NULL) { |
||||
|
inv->msatoshi = tal(inv, u64); |
||||
|
*inv->msatoshi = sqlite3_column_int64(stmt, 5); |
||||
|
} else { |
||||
|
inv->msatoshi = NULL; |
||||
|
} |
||||
|
|
||||
|
inv->expiry_time = sqlite3_column_int64(stmt, 6); |
||||
|
/* 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); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
struct invoices *invoices_new(const tal_t *ctx, |
||||
|
struct db *db, |
||||
|
struct log *log) |
||||
|
{ |
||||
|
struct invoices *invs = tal(ctx, struct invoices); |
||||
|
|
||||
|
invs->db = db; |
||||
|
invs->log = log; |
||||
|
|
||||
|
list_head_init(&invs->invlist); |
||||
|
list_head_init(&invs->waitany_waiters); |
||||
|
|
||||
|
return invs; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
bool invoices_load(struct invoices *invoices) |
||||
|
{ |
||||
|
int count = 0; |
||||
|
struct invoice *i; |
||||
|
sqlite3_stmt *stmt; |
||||
|
|
||||
|
/* Load invoices from db. */ |
||||
|
stmt = db_query(__func__, invoices->db, |
||||
|
"SELECT id, state, payment_key, payment_hash" |
||||
|
" , label, msatoshi, expiry_time, pay_index" |
||||
|
" , msatoshi_received" |
||||
|
" FROM invoices;"); |
||||
|
if (!stmt) { |
||||
|
log_broken(invoices->log, "Could not load invoices"); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) { |
||||
|
i = tal(invoices, struct invoice); |
||||
|
if (!wallet_stmt2invoice(stmt, i)) { |
||||
|
log_broken(invoices->log, "Error deserializing invoice"); |
||||
|
sqlite3_finalize(stmt); |
||||
|
return false; |
||||
|
} |
||||
|
list_add_tail(&invoices->invlist, &i->list); |
||||
|
count++; |
||||
|
} |
||||
|
log_debug(invoices->log, "Loaded %d invoices from DB", count); |
||||
|
|
||||
|
sqlite3_finalize(stmt); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
const struct invoice *invoices_create(struct invoices *invoices, |
||||
|
u64 *msatoshi TAKES, |
||||
|
const char *label TAKES, |
||||
|
u64 expiry) |
||||
|
{ |
||||
|
sqlite3_stmt *stmt; |
||||
|
struct invoice *invoice; |
||||
|
struct preimage r; |
||||
|
struct sha256 rhash; |
||||
|
u64 expiry_time; |
||||
|
|
||||
|
if (invoices_find_by_label(invoices, label)) { |
||||
|
if (taken(msatoshi)) |
||||
|
tal_free(msatoshi); |
||||
|
if (taken(label)) |
||||
|
tal_free(label); |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
/* Compute expiration. */ |
||||
|
expiry_time = time_now().ts.tv_sec + expiry; |
||||
|
/* Generate random secret preimage and hash. */ |
||||
|
randombytes_buf(r.r, sizeof(r.r)); |
||||
|
sha256(&rhash, r.r, sizeof(r.r)); |
||||
|
|
||||
|
/* Save to database. */ |
||||
|
/* Need to use the lower level API of sqlite3 to bind
|
||||
|
* label. Otherwise we'd need to implement sanitization of |
||||
|
* 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);"); |
||||
|
|
||||
|
sqlite3_bind_blob(stmt, 1, &rhash, sizeof(rhash), SQLITE_TRANSIENT); |
||||
|
sqlite3_bind_blob(stmt, 2, &r, sizeof(r), SQLITE_TRANSIENT); |
||||
|
sqlite3_bind_int(stmt, 3, UNPAID); |
||||
|
if (msatoshi) |
||||
|
sqlite3_bind_int64(stmt, 4, *msatoshi); |
||||
|
else |
||||
|
sqlite3_bind_null(stmt, 4); |
||||
|
sqlite3_bind_text(stmt, 5, label, strlen(label), SQLITE_TRANSIENT); |
||||
|
sqlite3_bind_int64(stmt, 6, expiry_time); |
||||
|
|
||||
|
db_exec_prepared(invoices->db, stmt); |
||||
|
|
||||
|
/* Create and load in-memory structure. */ |
||||
|
invoice = tal(invoices, struct invoice); |
||||
|
|
||||
|
invoice->id = sqlite3_last_insert_rowid(invoices->db->sql); |
||||
|
invoice->state = UNPAID; |
||||
|
invoice->label = tal_strdup(invoice, label); |
||||
|
invoice->msatoshi = tal_dup(invoice, u64, msatoshi); /* Works even if msatoshi == NULL. */ |
||||
|
memcpy(&invoice->r, &r, sizeof(invoice->r)); |
||||
|
memcpy(&invoice->rhash, &rhash, sizeof(invoice->rhash)); |
||||
|
invoice->expiry_time = expiry_time; |
||||
|
invoice->pay_index = 0; |
||||
|
list_head_init(&invoice->waitone_waiters); |
||||
|
|
||||
|
/* Add to invoices object. */ |
||||
|
list_add_tail(&invoices->invlist, &invoice->list); |
||||
|
|
||||
|
return invoice; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const struct invoice *invoices_find_by_label(struct invoices *invoices, |
||||
|
const char *label) |
||||
|
{ |
||||
|
struct invoice *i; |
||||
|
|
||||
|
/* FIXME: Use something better than a linear scan. */ |
||||
|
list_for_each(&invoices->invlist, i, list) { |
||||
|
if (streq(i->label, label)) |
||||
|
return i; |
||||
|
} |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
const struct invoice *invoices_find_unpaid(struct invoices *invoices, |
||||
|
const struct sha256 *rhash) |
||||
|
{ |
||||
|
struct invoice *i; |
||||
|
|
||||
|
list_for_each(&invoices->invlist, i, list) { |
||||
|
if (structeq(rhash, &i->rhash) && i->state == UNPAID) { |
||||
|
if (time_now().ts.tv_sec > i->expiry_time) |
||||
|
break; |
||||
|
return i; |
||||
|
} |
||||
|
} |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
bool invoices_delete(struct invoices *invoices, |
||||
|
const struct invoice *cinvoice) |
||||
|
{ |
||||
|
sqlite3_stmt *stmt; |
||||
|
struct invoice_waiter *w; |
||||
|
struct invoice *invoice = (struct invoice *) cinvoice; |
||||
|
const tal_t *tmpctx = tal_tmpctx(NULL); |
||||
|
|
||||
|
/* Delete from database. */ |
||||
|
stmt = db_prepare(invoices->db, "DELETE FROM invoices WHERE id=?;"); |
||||
|
sqlite3_bind_int64(stmt, 1, invoice->id); |
||||
|
db_exec_prepared(invoices->db, stmt); |
||||
|
|
||||
|
if (sqlite3_changes(invoices->db->sql) != 1) |
||||
|
return false; |
||||
|
|
||||
|
/* Delete from invoices object. */ |
||||
|
list_del_from(&invoices->invlist, &invoice->list); |
||||
|
|
||||
|
/* Tell all the waiters about the fact that it was deleted. */ |
||||
|
while ((w = list_pop(&invoice->waitone_waiters, |
||||
|
struct invoice_waiter, |
||||
|
list)) != NULL) { |
||||
|
/* Acquire the watcher for ourself first. */ |
||||
|
tal_steal(tmpctx, w); |
||||
|
trigger_invoice_waiter(w, NULL); |
||||
|
} |
||||
|
|
||||
|
/* Free all watchers and the invoice. */ |
||||
|
tal_free(tmpctx); |
||||
|
tal_free(invoice); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
const struct invoice *invoices_iterate(struct invoices *invoices, |
||||
|
const struct invoice *invoice) |
||||
|
{ |
||||
|
if (invoice) |
||||
|
return list_next(&invoices->invlist, invoice, list); |
||||
|
else |
||||
|
return list_top(&invoices->invlist, struct invoice, list); |
||||
|
} |
||||
|
|
||||
|
static s64 get_next_pay_index(struct db *db) |
||||
|
{ |
||||
|
/* Equivalent to (next_pay_index++) */ |
||||
|
s64 next_pay_index; |
||||
|
next_pay_index = db_get_intvar(db, "next_pay_index", 0); |
||||
|
/* Variable should exist. */ |
||||
|
assert(next_pay_index > 0); |
||||
|
db_set_intvar(db, "next_pay_index", next_pay_index + 1); |
||||
|
return next_pay_index; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void invoices_resolve(struct invoices *invoices, |
||||
|
const struct invoice *cinvoice, |
||||
|
u64 msatoshi_received) |
||||
|
{ |
||||
|
sqlite3_stmt *stmt; |
||||
|
struct invoice_waiter *w; |
||||
|
struct invoice *invoice = (struct invoice *)cinvoice; |
||||
|
s64 pay_index; |
||||
|
const tal_t *tmpctx = tal_tmpctx(NULL); |
||||
|
|
||||
|
/* Assign a pay-index. */ |
||||
|
pay_index = get_next_pay_index(invoices->db); |
||||
|
/* FIXME: Save time of payment. */ |
||||
|
|
||||
|
/* Update database. */ |
||||
|
stmt = db_prepare(invoices->db, |
||||
|
"UPDATE invoices" |
||||
|
" SET state=?" |
||||
|
" , pay_index=?" |
||||
|
" , msatoshi_received=?" |
||||
|
" 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); |
||||
|
db_exec_prepared(invoices->db, stmt); |
||||
|
|
||||
|
/* Update in-memory structure. */ |
||||
|
invoice->state = PAID; |
||||
|
invoice->pay_index = pay_index; |
||||
|
invoice->msatoshi_received = msatoshi_received; |
||||
|
|
||||
|
/* Tell all the waitany waiters about the new paid invoice. */ |
||||
|
while ((w = list_pop(&invoices->waitany_waiters, |
||||
|
struct invoice_waiter, |
||||
|
list)) != NULL) { |
||||
|
tal_steal(tmpctx, w); |
||||
|
trigger_invoice_waiter(w, invoice); |
||||
|
} |
||||
|
/* Tell any waitinvoice waiters about the specific invoice
|
||||
|
* getting paid. */ |
||||
|
while ((w = list_pop(&invoice->waitone_waiters, |
||||
|
struct invoice_waiter, |
||||
|
list)) != NULL) { |
||||
|
tal_steal(tmpctx, w); |
||||
|
trigger_invoice_waiter(w, invoice); |
||||
|
} |
||||
|
|
||||
|
/* Free all watchers. */ |
||||
|
tal_free(tmpctx); |
||||
|
} |
||||
|
|
||||
|
/* Called when an invoice waiter is destructed. */ |
||||
|
static void invoice_waiter_dtor(struct invoice_waiter *w) |
||||
|
{ |
||||
|
/* Already triggered. */ |
||||
|
if (w->triggered) |
||||
|
return; |
||||
|
list_del(&w->list); |
||||
|
} |
||||
|
|
||||
|
/* Add an invoice waiter to the specified list of invoice waiters. */ |
||||
|
static void add_invoice_waiter(const tal_t *ctx, |
||||
|
struct list_head *waiters, |
||||
|
void (*cb)(const struct invoice *, void*), |
||||
|
void* cbarg) |
||||
|
{ |
||||
|
struct invoice_waiter *w = tal(ctx, struct invoice_waiter); |
||||
|
w->triggered = false; |
||||
|
list_add_tail(waiters, &w->list); |
||||
|
w->cb = cb; |
||||
|
w->cbarg = cbarg; |
||||
|
tal_add_destructor(w, &invoice_waiter_dtor); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void invoices_waitany(const tal_t *ctx, |
||||
|
struct invoices *invoices, |
||||
|
u64 lastpay_index, |
||||
|
void (*cb)(const struct invoice *, void*), |
||||
|
void *cbarg) |
||||
|
{ |
||||
|
sqlite3_stmt *stmt; |
||||
|
const struct invoice *invoice; |
||||
|
int res; |
||||
|
char const* label; |
||||
|
|
||||
|
/* Look for an already-paid invoice. */ |
||||
|
stmt = db_prepare(invoices->db, |
||||
|
"SELECT label" |
||||
|
" FROM invoices" |
||||
|
" WHERE pay_index NOT NULL" |
||||
|
" AND pay_index > ?" |
||||
|
" ORDER BY pay_index ASC LIMIT 1;"); |
||||
|
sqlite3_bind_int64(stmt, 1, lastpay_index); |
||||
|
|
||||
|
res = sqlite3_step(stmt); |
||||
|
if (res == SQLITE_ROW) { |
||||
|
/* Invoice found. Look up the invoice object. */ |
||||
|
label = tal_strndup(ctx, sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0)); |
||||
|
sqlite3_finalize(stmt); |
||||
|
|
||||
|
/* The invoice should definitely exist in-memory. */ |
||||
|
invoice = invoices_find_by_label(invoices, label); |
||||
|
assert(invoice); |
||||
|
tal_free(label); |
||||
|
|
||||
|
cb(invoice, cbarg); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
sqlite3_finalize(stmt); |
||||
|
|
||||
|
/* None found. */ |
||||
|
add_invoice_waiter(ctx, &invoices->waitany_waiters, cb, cbarg); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void invoices_waitone(const tal_t *ctx, |
||||
|
struct invoices *invoices, |
||||
|
struct invoice const *cinvoice, |
||||
|
void (*cb)(const struct invoice *, void*), |
||||
|
void *cbarg) |
||||
|
{ |
||||
|
struct invoice *invoice = (struct invoice*) cinvoice; |
||||
|
/* FIXME: Handle expired state. */ |
||||
|
if (invoice->state == PAID) { |
||||
|
cb(invoice, cbarg); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
/* Not yet paid. */ |
||||
|
add_invoice_waiter(ctx, &invoice->waitone_waiters, cb, cbarg); |
||||
|
} |
@ -0,0 +1,160 @@ |
|||||
|
#ifndef LIGHTNING_WALLET_INVOICES_H |
||||
|
#define LIGHTNING_WALLET_INVOICES_H |
||||
|
#include "config.h" |
||||
|
#include <ccan/short_types/short_types.h> |
||||
|
#include <ccan/tal/tal.h> |
||||
|
#include <ccan/take/take.h> |
||||
|
|
||||
|
struct db; |
||||
|
struct invoice; |
||||
|
struct invoices; |
||||
|
struct log; |
||||
|
struct sha256; |
||||
|
|
||||
|
/**
|
||||
|
* invoices_new - Constructor for a new invoice handler |
||||
|
* |
||||
|
* @ctx - the owner of the invoice handler. |
||||
|
* @db - the database connection to use for saving invoice. |
||||
|
* @log - the log to report to. |
||||
|
*/ |
||||
|
struct invoices *invoices_new(const tal_t *ctx, |
||||
|
struct db *db, |
||||
|
struct log *log); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_load - Second-stage constructor for invoice handler. |
||||
|
* Must be called before the other functions are called |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
*/ |
||||
|
bool invoices_load(struct invoices *invoices); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_create - Create a new invoice. |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @msatoshi - the amount the invoice should have, or |
||||
|
* NULL for any-amount invoices. |
||||
|
* @label - the unique label for this invoice. Must be |
||||
|
* non-NULL. Must be null-terminated. |
||||
|
* @expiry - the number of seconds before the invoice |
||||
|
* expires |
||||
|
* |
||||
|
* Returns NULL if label already exists or expiry is 0. |
||||
|
* FIXME: Fallback addresses |
||||
|
*/ |
||||
|
const struct invoice *invoices_create(struct invoices *invoices, |
||||
|
u64 *msatoshi TAKES, |
||||
|
const char *label TAKES, |
||||
|
u64 expiry); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_find_by_label - Search for an invoice by label |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @label - the label to search for. Must be null-terminated. |
||||
|
* |
||||
|
* Returns NULL if no invoice with that label exists. |
||||
|
*/ |
||||
|
const struct invoice *invoices_find_by_label(struct invoices *invoices, |
||||
|
const char *label); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_find_unpaid - Search for an unpaid, unexpired invoice by |
||||
|
* payment_hash |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @rhash - the payment_hash to search for. |
||||
|
* |
||||
|
* Rerturns NULL if no invoice with that payment hash exists. |
||||
|
*/ |
||||
|
const struct invoice *invoices_find_unpaid(struct invoices *invoices, |
||||
|
const struct sha256 *rhash); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_delete - Delete an invoice |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @invoice - the invoice to delete. |
||||
|
* |
||||
|
* Return false on failure. |
||||
|
*/ |
||||
|
bool invoices_delete(struct invoices *invoices, |
||||
|
const struct invoice *invoice); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_iterate - Iterate over all existing invoices |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @invoice - the previous invoice you iterated over. |
||||
|
* |
||||
|
* Return NULL at end-of-sequence. Usage: |
||||
|
* |
||||
|
* const struct invoice *i; |
||||
|
* i = NULL; |
||||
|
* while ((i = invoices_iterate(invoices, i))) { |
||||
|
* ... |
||||
|
* } |
||||
|
*/ |
||||
|
const struct invoice *invoices_iterate(struct invoices *invoices, |
||||
|
const struct invoice *invoice); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_resolve - Mark an invoice as paid |
||||
|
* |
||||
|
* @invoices - the invoice handler. |
||||
|
* @invoice - the invoice to mark as paid. |
||||
|
* @msatoshi_received - the actual amount received. |
||||
|
* |
||||
|
* Precondition: the invoice must not yet be expired (invoices |
||||
|
* does not check). |
||||
|
*/ |
||||
|
void invoices_resolve(struct invoices *invoices, |
||||
|
const struct invoice *invoice, |
||||
|
u64 msatoshi_received); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_waitany - Wait for any invoice to be paid. |
||||
|
* |
||||
|
* @ctx - the owner of the callback. If the owner is freed, |
||||
|
* the callback is cancelled. |
||||
|
* @invoices - the invoice handler. |
||||
|
* @lastpay_index - wait for invoices after the specified |
||||
|
* pay_index. Use 0 to wait for the first invoice. |
||||
|
* @cb - the callback to invoke. If an invoice is already |
||||
|
* paid with pay_index greater than lastpay_index, this |
||||
|
* is called immediately, otherwise it is called during |
||||
|
* an invoices_resolve call. |
||||
|
* @cbarg - the callback data. |
||||
|
*/ |
||||
|
void invoices_waitany(const tal_t *ctx, |
||||
|
struct invoices *invoices, |
||||
|
u64 lastpay_index, |
||||
|
void (*cb)(const struct invoice *, void*), |
||||
|
void *cbarg); |
||||
|
|
||||
|
/**
|
||||
|
* invoices_waitone - Wait for a specific invoice to be paid, |
||||
|
* deleted, or expired. |
||||
|
* |
||||
|
* @ctx - the owner of the callback. If the owner is freed, |
||||
|
* the callback is cancelled. |
||||
|
* @invoices - the invoice handler, |
||||
|
* @invoice - the invoice to wait on. |
||||
|
* @cb - the callback to invoice. If invoice is already paid |
||||
|
* or expired, this is called immediately, otherwise it is |
||||
|
* called during an invoices_resolve or invoices_delete call. |
||||
|
* If the invoice was deleted, the callback is given a NULL |
||||
|
* invoice. |
||||
|
* @cbarg - the callback data. |
||||
|
* |
||||
|
* FIXME: actually trigger on expired invoices. |
||||
|
*/ |
||||
|
void invoices_waitone(const tal_t *ctx, |
||||
|
struct invoices *invoices, |
||||
|
struct invoice const *invoice, |
||||
|
void (*cb)(const struct invoice *, void*), |
||||
|
void *cbarg); |
||||
|
|
||||
|
#endif /* LIGHTNING_WALLET_INVOICES_H */ |
Loading…
Reference in new issue