diff --git a/lightningd/invoice.c b/lightningd/invoice.c index d4b5ff220..d68e43a82 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -18,62 +18,6 @@ #include #include -struct invoice_waiter { - struct list_node list; - struct command *cmd; -}; - -/* FIXME: remove this, just use database ops. */ -struct invoices { - /* Payments for r values we know about. */ - struct list_head invlist; - /* Waiting for new invoices to be paid. */ - struct list_head waitany_waiters; -}; - -struct invoice *find_unpaid(struct invoices *invs, const struct sha256 *rhash) -{ - struct invoice *i; - - list_for_each(&invs->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; -} - -static struct invoice *find_invoice_by_label(const struct invoices *invs, - const char *label) -{ - struct invoice *i; - - list_for_each(&invs->invlist, i, list) { - if (streq(i->label, label)) - return i; - } - return NULL; -} - -void invoice_add(struct invoices *invs, - struct invoice *inv) -{ - sha256(&inv->rhash, inv->r.r, sizeof(inv->r.r)); - list_add(&invs->invlist, &inv->list); -} - -struct invoices *invoices_init(const tal_t *ctx) -{ - struct invoices *invs = tal(ctx, struct invoices); - - list_head_init(&invs->invlist); - list_head_init(&invs->waitany_waiters); - - return invs; -} - static void json_add_invoice(struct json_result *response, const struct invoice *inv) { @@ -99,39 +43,16 @@ static void tell_waiter(struct command *cmd, const struct invoice *paid) json_add_invoice(response, paid); command_success(cmd, response); } -static void tell_waiter_deleted(struct command *cmd, const struct invoice *paid) +static void tell_waiter_deleted(struct command *cmd) { command_fail(cmd, "invoice deleted during wait"); } - -void resolve_invoice(struct lightningd *ld, struct invoice *invoice, - u64 msatoshi_received) +static void wait_on_invoice(const struct invoice *invoice, void *cmd) { - 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. */ - wallet_invoice_save(ld->wallet, invoice); - - /* Yes, there are two loops: the first is for wait*any*invoice, - * the second is for waitinvoice (without any). */ - /* Tell all the waitanyinvoice waiters about the new paid invoice */ - while ((w = list_pop(&invs->waitany_waiters, - struct invoice_waiter, - list)) != NULL) - tell_waiter(w->cmd, invoice); - /* Tell any waitinvoice waiters about the invoice getting paid. */ - while ((w = list_pop(&invoice->waitone_waiters, - struct invoice_waiter, - list)) != NULL) - tell_waiter(w->cmd, invoice); - - /* Also mark the payment in the history table as complete */ - wallet_payment_set_status(ld->wallet, &invoice->rhash, PAYMENT_COMPLETE); + if (invoice) + tell_waiter((struct command *) cmd, invoice); + else + tell_waiter_deleted((struct command *) cmd); } static bool hsm_sign_b11(const u5 *u5bytes, @@ -153,38 +74,15 @@ static bool hsm_sign_b11(const u5 *u5bytes, return true; } -/* Return NULL if no error, or an error string otherwise. */ -static char *delete_invoice(const tal_t *cxt, - struct wallet *wallet, - struct invoices *invs, - struct invoice *i) -{ - struct invoice_waiter *w; - if (!wallet_invoice_remove(wallet, i)) { - return tal_strdup(cxt, "Database error"); - } - list_del_from(&invs->invlist, &i->list); - - /* Tell all the waiters about the fact that it was deleted. */ - while ((w = list_pop(&i->waitone_waiters, - struct invoice_waiter, - list)) != NULL) { - tell_waiter_deleted(w->cmd, i); - /* No need to free w: w is a sub-object of cmd, - * and tell_waiter_deleted also deletes the cmd. */ - } - - tal_free(i); - return NULL; -} - static void json_invoice(struct command *cmd, const char *buffer, const jsmntok_t *params) { - struct invoice *invoice; + const struct invoice *invoice; jsmntok_t *msatoshi, *label, *desc, *exp; + u64 *msatoshi_val; + const char *label_val; struct json_result *response = new_json_result(cmd); - struct invoices *invs = cmd->ld->invoices; + struct wallet *wallet = cmd->ld->wallet; struct bolt11 *b11; char *b11enc; struct wallet_payment payment; @@ -200,21 +98,14 @@ static void json_invoice(struct command *cmd, return; } - invoice = tal(cmd, struct invoice); - invoice->id = 0; - invoice->state = UNPAID; - invoice->pay_index = 0; - list_head_init(&invoice->waitone_waiters); - randombytes_buf(invoice->r.r, sizeof(invoice->r.r)); - - sha256(&invoice->rhash, invoice->r.r, sizeof(invoice->r.r)); - + /* Get arguments. */ + /* msatoshi */ if (json_tok_streq(buffer, msatoshi, "any")) - invoice->msatoshi = NULL; + msatoshi_val = NULL; else { - invoice->msatoshi = tal(invoice, u64); - if (!json_tok_u64(buffer, msatoshi, invoice->msatoshi) - || *invoice->msatoshi == 0) { + msatoshi_val = tal(cmd, u64); + if (!json_tok_u64(buffer, msatoshi, msatoshi_val) + || *msatoshi_val == 0) { command_fail(cmd, "'%.*s' is not a valid positive number", msatoshi->end - msatoshi->start, @@ -222,19 +113,18 @@ static void json_invoice(struct command *cmd, return; } } - - invoice->label = tal_strndup(invoice, buffer + label->start, - label->end - label->start); - if (find_invoice_by_label(invs, invoice->label)) { - command_fail(cmd, "Duplicate label '%s'", invoice->label); + /* label */ + label_val = tal_strndup(cmd, buffer + label->start, + label->end - label->start); + if (wallet_invoice_find_by_label(wallet, label_val)) { + command_fail(cmd, "Duplicate label '%s'", label_val); return; } - if (strlen(invoice->label) > INVOICE_MAX_LABEL_LEN) { - command_fail(cmd, "label '%s' over %u bytes", invoice->label, + if (strlen(label_val) > INVOICE_MAX_LABEL_LEN) { + command_fail(cmd, "label '%s' over %u bytes", label_val, INVOICE_MAX_LABEL_LEN); return; } - if (exp && !json_tok_u64(buffer, exp, &expiry)) { command_fail(cmd, "expiry '%.*s' invalid seconds", exp->end - exp->start, @@ -242,10 +132,14 @@ static void json_invoice(struct command *cmd, return; } - /* Expires at this absolute time. */ - invoice->expiry_time = time_now().ts.tv_sec + expiry; - - wallet_invoice_save(cmd->ld->wallet, invoice); + invoice = wallet_invoice_create(cmd->ld->wallet, + take(msatoshi_val), + take(label_val), + expiry); + if (!invoice) { + command_fail(cmd, "Failed to create invoice on database"); + return; + } /* Construct bolt11 string. */ b11 = new_bolt11(cmd, invoice->msatoshi); @@ -266,10 +160,6 @@ static void json_invoice(struct command *cmd, /* FIXME: add private routes if necessary! */ b11enc = bolt11_encode(cmd, b11, false, hsm_sign_b11, cmd->ld); - /* OK, connect it to main state, respond with hash */ - tal_steal(invs, invoice); - list_add_tail(&invs->invlist, &invoice->list); - /* Store the payment so we can later show it in the history */ payment.id = 0; payment.incoming = true; @@ -308,15 +198,16 @@ static const struct json_command invoice_command = { AUTODATA(json_command, &invoice_command); static void json_add_invoices(struct json_result *response, - const struct list_head *list, + struct wallet *wallet, const char *buffer, const jsmntok_t *label) { - struct invoice *i; + const struct invoice *i; char *lbl = NULL; if (label) lbl = tal_strndup(response, &buffer[label->start], label->end - label->start); - list_for_each(list, i, list) { + i = NULL; + while ((i = wallet_invoice_iterate(wallet, i)) != NULL) { if (lbl && !streq(i->label, lbl)) continue; json_add_invoice(response, i); @@ -328,7 +219,7 @@ static void json_listinvoice(struct command *cmd, { jsmntok_t *label = NULL; struct json_result *response = new_json_result(cmd); - struct invoices *invs = cmd->ld->invoices; + struct wallet *wallet = cmd->ld->wallet; if (!json_get_params(buffer, params, "?label", &label, @@ -339,7 +230,7 @@ static void json_listinvoice(struct command *cmd, json_array_start(response, NULL); - json_add_invoices(response, &invs->invlist, buffer, label); + json_add_invoices(response, wallet, buffer, label); json_array_end(response); command_success(cmd, response); } @@ -355,12 +246,12 @@ AUTODATA(json_command, &listinvoice_command); static void json_delinvoice(struct command *cmd, const char *buffer, const jsmntok_t *params) { - struct invoice *i; + const struct invoice *i; jsmntok_t *labeltok; struct json_result *response = new_json_result(cmd); const char *label; - struct invoices *invs = cmd->ld->invoices; - char *error; + struct wallet *wallet = cmd->ld->wallet; + bool error; if (!json_get_params(buffer, params, "label", &labeltok, @@ -371,7 +262,7 @@ static void json_delinvoice(struct command *cmd, label = tal_strndup(cmd, buffer + labeltok->start, labeltok->end - labeltok->start); - i = find_invoice_by_label(invs, label); + i = wallet_invoice_find_by_label(wallet, label); if (!i) { command_fail(cmd, "Unknown invoice"); return; @@ -381,15 +272,12 @@ static void json_delinvoice(struct command *cmd, * otherwise the invoice will be freed. */ json_add_invoice(response, i); - error = delete_invoice(cmd, cmd->ld->wallet, invs, i); + error = wallet_invoice_delete(wallet, i); if (error) { log_broken(cmd->ld->log, "Error attempting to remove invoice %"PRIu64, i->id); - command_fail(cmd, "%s", error); - /* Both error and response are sub-objects of cmd, - * and command_fail will free cmd (and also error - * and response). */ + command_fail(cmd, "Database error"); return; } @@ -409,11 +297,7 @@ static void json_waitanyinvoice(struct command *cmd, { jsmntok_t *pay_indextok; u64 pay_index; - struct invoice_waiter *w; - struct invoices *invs = cmd->ld->invoices; struct wallet *wallet = cmd->ld->wallet; - struct invoice *inv; - struct json_result *response; if (!json_get_params(buffer, params, "?lastpay_index", &pay_indextok, @@ -433,27 +317,14 @@ static void json_waitanyinvoice(struct command *cmd, } } - /* Find next paid invoice. */ - inv = wallet_invoice_nextpaid(cmd, wallet, pay_index); - - /* If we found one, return it. */ - if (inv) { - response = new_json_result(cmd); - - json_add_invoice(response, inv); - command_success(cmd, response); - - /* inv is freed when cmd is freed, and command_success - * also frees cmd. */ - return; - } - - /* Otherwise, wait. */ - /* FIXME: Better to use io_wait directly? */ - w = tal(cmd, struct invoice_waiter); - w->cmd = cmd; - list_add_tail(&invs->waitany_waiters, &w->list); + /* Set command as pending. We do not know if + * wallet_invoice_waitany will return immediately + * or not, so indicating pending is safest. */ command_still_pending(cmd); + + /* Find next paid invoice. */ + wallet_invoice_waitany(cmd, wallet, pay_index, + &wait_on_invoice, (void*) cmd); } static const struct json_command waitanyinvoice_command = { @@ -473,11 +344,10 @@ AUTODATA(json_command, &waitanyinvoice_command); static void json_waitinvoice(struct command *cmd, const char *buffer, const jsmntok_t *params) { - struct invoice *i; + const struct invoice *i; + struct wallet *wallet = cmd->ld->wallet; jsmntok_t *labeltok; const char *label = NULL; - struct invoice_waiter *w; - struct invoices *invs = cmd->ld->invoices; if (!json_get_params(buffer, params, "label", &labeltok, NULL)) { command_fail(cmd, "Missing {label}"); @@ -486,7 +356,7 @@ static void json_waitinvoice(struct command *cmd, /* Search in paid invoices, if found return immediately */ label = tal_strndup(cmd, buffer + labeltok->start, labeltok->end - labeltok->start); - i = find_invoice_by_label(invs, label); + i = wallet_invoice_find_by_label(wallet, label); if (!i) { command_fail(cmd, "Label not found"); @@ -496,10 +366,9 @@ static void json_waitinvoice(struct command *cmd, return; } else { /* There is an unpaid one matching, let's wait... */ - w = tal(cmd, struct invoice_waiter); - w->cmd = cmd; - list_add_tail(&i->waitone_waiters, &w->list); command_still_pending(cmd); + wallet_invoice_waitone(cmd, wallet, i, + &wait_on_invoice, (void *) cmd); } } diff --git a/lightningd/invoice.h b/lightningd/invoice.h index 8206fe634..a7b615780 100644 --- a/lightningd/invoice.h +++ b/lightningd/invoice.h @@ -6,47 +6,4 @@ #include #include -struct invoices; -struct lightningd; - -/* /!\ This is a DB ENUM, please do not change the numbering of any - * already defined elements (adding is ok) /!\ */ -enum invoice_status { - UNPAID, - PAID, -}; - -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; -}; - -#define INVOICE_MAX_LABEL_LEN 128 - -/* From database */ -void invoice_add(struct invoices *invs, - struct invoice *inv); - -void resolve_invoice(struct lightningd *ld, struct invoice *invoice, - u64 msatoshi_received); - -struct invoice *find_unpaid(struct invoices *i, - const struct sha256 *rhash); - -struct invoices *invoices_init(const tal_t *ctx); #endif /* LIGHTNING_LIGHTNINGD_INVOICE_H */ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 34a3b3ac8..7204f462f 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -64,9 +64,6 @@ static struct lightningd *new_lightningd(const tal_t *ctx, timers_init(&ld->timers, time_mono()); ld->topology = new_topology(ld, ld->log); - /* FIXME: Move into invoice daemon. */ - ld->invoices = invoices_init(ld); - return ld; } @@ -295,8 +292,8 @@ int main(int argc, char *argv[]) /* Initialize the transaction filter with our pubkeys. */ init_txfilter(ld->wallet, ld->owned_txfilter); - /* Load invoices from the database */ - if (!wallet_invoices_load(ld->wallet, ld->invoices)) { + /* Check invoices loaded from the database */ + if (!wallet_invoice_load(ld->wallet)) { fatal("Could not load invoices from the database"); } diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index 361759dd7..6f21a16c5 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -20,6 +19,7 @@ #include #include #include +#include #include static bool state_update_ok(struct peer *peer, @@ -222,7 +222,7 @@ static void handle_localpay(struct htlc_in *hin, u32 outgoing_cltv_value) { enum onion_type failcode; - struct invoice *invoice; + const struct invoice *invoice; struct lightningd *ld = hin->key.peer->ld; /* BOLT #4: @@ -253,7 +253,7 @@ static void handle_localpay(struct htlc_in *hin, goto fail; } - invoice = find_unpaid(ld->invoices, payment_hash); + invoice = wallet_invoice_find_unpaid(ld->wallet, payment_hash); if (!invoice) { failcode = WIRE_UNKNOWN_PAYMENT_HASH; goto fail; @@ -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, hin->msatoshi); + wallet_invoice_resolve(ld->wallet, invoice, hin->msatoshi); return; fail: diff --git a/lightningd/test/run-find_my_path.c b/lightningd/test/run-find_my_path.c index 7630e1685..3cd66830a 100644 --- a/lightningd/test/run-find_my_path.c +++ b/lightningd/test/run-find_my_path.c @@ -43,9 +43,6 @@ size_t hash_htlc_key(const struct htlc_key *htlc_key UNNEEDED) /* Generated stub for hsm_init */ void hsm_init(struct lightningd *ld UNNEEDED, bool newdir UNNEEDED) { fprintf(stderr, "hsm_init called!\n"); abort(); } -/* Generated stub for invoices_init */ -struct invoices *invoices_init(const tal_t *ctx UNNEEDED) -{ fprintf(stderr, "invoices_init called!\n"); abort(); } /* Generated stub for log_ */ void log_(struct log *log UNNEEDED, enum log_level level UNNEEDED, const char *fmt UNNEEDED, ...) @@ -111,9 +108,9 @@ bool wallet_htlcs_reconnect(struct wallet *wallet UNNEEDED, struct htlc_in_map *htlcs_in UNNEEDED, struct htlc_out_map *htlcs_out UNNEEDED) { fprintf(stderr, "wallet_htlcs_reconnect called!\n"); abort(); } -/* Generated stub for wallet_invoices_load */ -bool wallet_invoices_load(struct wallet *wallet UNNEEDED, struct invoices *invs UNNEEDED) -{ fprintf(stderr, "wallet_invoices_load called!\n"); abort(); } +/* Generated stub for wallet_invoice_load */ +bool wallet_invoice_load(struct wallet *wallet UNNEEDED) +{ fprintf(stderr, "wallet_invoice_load called!\n"); abort(); } /* Generated stub for wallet_new */ struct wallet *wallet_new(const tal_t *ctx UNNEEDED, struct log *log UNNEEDED) { fprintf(stderr, "wallet_new called!\n"); abort(); } diff --git a/wallet/Makefile b/wallet/Makefile index f3347f201..66468e7e1 100644 --- a/wallet/Makefile +++ b/wallet/Makefile @@ -6,6 +6,7 @@ wallet-wrongdir: WALLET_LIB_SRC := \ wallet/db.c \ + wallet/invoices.c \ wallet/wallet.c \ wallet/walletrpc.c diff --git a/wallet/invoices.c b/wallet/invoices.c new file mode 100644 index 000000000..51fcdccc4 --- /dev/null +++ b/wallet/invoices.c @@ -0,0 +1,402 @@ +#include "db.h" +#include "invoices.h" +#include "wallet.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/wallet/invoices.h b/wallet/invoices.h new file mode 100644 index 000000000..196b79507 --- /dev/null +++ b/wallet/invoices.h @@ -0,0 +1,160 @@ +#ifndef LIGHTNING_WALLET_INVOICES_H +#define LIGHTNING_WALLET_INVOICES_H +#include "config.h" +#include +#include +#include + +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 */ diff --git a/wallet/test/Makefile b/wallet/test/Makefile index 47fb50cdf..baed27147 100644 --- a/wallet/test/Makefile +++ b/wallet/test/Makefile @@ -9,6 +9,7 @@ WALLET_TEST_COMMON_OBJS := \ common/pseudorand.o \ common/utils.o \ common/wireaddr.o \ + wallet/invoices.o \ wire/towire.o \ wire/fromwire.o \ lightningd/htlc_end.o \ diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 099af7f95..4a65a00ea 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -43,9 +43,6 @@ static void wallet_fatal(const char *fmt, ...) #define transaction_wrap(db, ...) \ (db_begin_transaction(db), __VA_ARGS__, db_commit_transaction(db), wallet_err == NULL) -void invoice_add(struct invoices *invs, - struct invoice *inv){} - /** * mempat -- Set the memory to a pattern * diff --git a/wallet/wallet.c b/wallet/wallet.c index a0efb1199..d4d32c721 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1,3 +1,4 @@ +#include "invoices.h" #include "wallet.h" #include @@ -20,6 +21,7 @@ struct wallet *wallet_new(const tal_t *ctx, struct log *log) wallet->db = db_setup(wallet, log); wallet->log = log; wallet->bip32_base = NULL; + wallet->invoices = invoices_new(wallet, wallet->db, log); return wallet; } @@ -1151,203 +1153,64 @@ bool wallet_htlcs_reconnect(struct wallet *wallet, return true; } -static void wallet_stmt2invoice(sqlite3_stmt *stmt, struct invoice *inv) +/* Almost all wallet_invoice_* functions delegate to the + * appropriate invoices_* function. */ +bool wallet_invoice_load(struct wallet *wallet) { - 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 invoices_load(wallet->invoices); +} +const struct invoice *wallet_invoice_create(struct wallet *wallet, + u64 *msatoshi TAKES, + const char *label TAKES, + u64 expiry) { + return invoices_create(wallet->invoices, msatoshi, label, expiry); +} +const struct invoice *wallet_invoice_find_by_label(struct wallet *wallet, + const char *label) +{ + return invoices_find_by_label(wallet->invoices, label); +} +const struct invoice *wallet_invoice_find_unpaid(struct wallet *wallet, + const struct sha256 *rhash) +{ + return invoices_find_unpaid(wallet->invoices, rhash); +} +bool wallet_invoice_delete(struct wallet *wallet, + const struct invoice *invoice) +{ + return invoices_delete(wallet->invoices, invoice); +} +const struct invoice *wallet_invoice_iterate(struct wallet *wallet, + const struct invoice *invoice) +{ + return invoices_iterate(wallet->invoices, invoice); +} +void wallet_invoice_resolve(struct wallet *wallet, + const struct invoice *invoice, + u64 msatoshi_received) +{ + invoices_resolve(wallet->invoices, invoice, msatoshi_received); + /* FIXME: consider payment recording. */ + wallet_payment_set_status(wallet, &invoice->rhash, PAYMENT_COMPLETE); +} +void wallet_invoice_waitany(const tal_t *ctx, + struct wallet *wallet, + u64 lastpay_index, + void (*cb)(const struct invoice *, void*), + void *cbarg) +{ + invoices_waitany(ctx, wallet->invoices, lastpay_index, cb, cbarg); +} +void wallet_invoice_waitone(const tal_t *ctx, + struct wallet *wallet, + struct invoice const *invoice, + void (*cb)(const struct invoice *, void*), + void *cbarg) +{ + invoices_waitone(ctx, wallet->invoices, invoice, cb, cbarg); } -struct invoice *wallet_invoice_nextpaid(const tal_t *ctx, - const struct wallet *wallet, - u64 pay_index) -{ - sqlite3_stmt *stmt; - int res; - struct invoice *inv = tal(ctx, struct invoice); - /* Generate query. */ - stmt = db_prepare(wallet->db, - "SELECT id, state, payment_key, payment_hash," - " label, msatoshi, expiry_time, pay_index," - " msatoshi_received " - " FROM invoices" - " WHERE pay_index NOT NULL" - " AND pay_index > ?" - " ORDER BY pay_index ASC LIMIT 1;"); - sqlite3_bind_int64(stmt, 1, pay_index); - - res = sqlite3_step(stmt); - if (res != SQLITE_ROW) { - /* No paid invoice found. */ - sqlite3_finalize(stmt); - return tal_free(inv); - } else { - wallet_stmt2invoice(stmt, inv); - - sqlite3_finalize(stmt); - return inv; - } -} - -/* Acquire the next pay_index. */ -static s64 wallet_invoice_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; -} -/* Determine the on-database state of an invoice. */ -static enum invoice_status wallet_invoice_db_state( - struct db *db, const struct invoice *inv) -{ - sqlite3_stmt *stmt; - int res; - int state; - - stmt = db_prepare(db, "SELECT state FROM invoices WHERE id=?;"); - - sqlite3_bind_int64(stmt, 1, inv->id); - - res = sqlite3_step(stmt); - assert(res == SQLITE_ROW); - state = sqlite3_column_int(stmt, 0); - res = sqlite3_step(stmt); - assert(res == SQLITE_DONE); - - sqlite3_finalize(stmt); - return (enum invoice_status) state; -} - -void wallet_invoice_save(struct wallet *wallet, struct invoice *inv) -{ - sqlite3_stmt *stmt; - bool unpaid_to_paid = false; - - /* 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... */ - if (!inv->id) { - stmt = db_prepare(wallet->db, - "INSERT INTO invoices (" - " payment_hash," - " payment_key," - " state," - " msatoshi," - " label," - " expiry_time," - " pay_index," - " msatoshi_received" - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); - - sqlite3_bind_blob(stmt, 1, &inv->rhash, sizeof(inv->rhash), SQLITE_TRANSIENT); - sqlite3_bind_blob(stmt, 2, &inv->r, sizeof(inv->r), SQLITE_TRANSIENT); - sqlite3_bind_int(stmt, 3, inv->state); - if (inv->msatoshi) { - sqlite3_bind_int64(stmt, 4, *inv->msatoshi); - } else { - sqlite3_bind_null(stmt, 4); - } - sqlite3_bind_text(stmt, 5, inv->label, strlen(inv->label), SQLITE_TRANSIENT); - sqlite3_bind_int64(stmt, 6, inv->expiry_time); - if (inv->state == PAID) { - inv->pay_index = wallet_invoice_next_pay_index(wallet->db); - sqlite3_bind_int64(stmt, 7, inv->pay_index); - sqlite3_bind_int64(stmt, 8, inv->msatoshi_received); - } else { - sqlite3_bind_null(stmt, 7); - sqlite3_bind_null(stmt, 8); - } - - db_exec_prepared(wallet->db, stmt); - - inv->id = sqlite3_last_insert_rowid(wallet->db->sql); - } else { - if (inv->state == PAID) { - if (wallet_invoice_db_state(wallet->db, inv) == UNPAID) { - unpaid_to_paid = true; - } - } - if (unpaid_to_paid) { - stmt = db_prepare(wallet->db, "UPDATE invoices SET state=?, pay_index=?, msatoshi_received=? WHERE id=?;"); - - sqlite3_bind_int(stmt, 1, inv->state); - inv->pay_index = wallet_invoice_next_pay_index(wallet->db); - sqlite3_bind_int64(stmt, 2, inv->pay_index); - sqlite3_bind_int64(stmt, 3, inv->msatoshi_received); - sqlite3_bind_int64(stmt, 4, inv->id); - - db_exec_prepared(wallet->db, stmt); - } else { - stmt = db_prepare(wallet->db, "UPDATE invoices SET state=? WHERE id=?;"); - - sqlite3_bind_int(stmt, 1, inv->state); - sqlite3_bind_int64(stmt, 2, inv->id); - - db_exec_prepared(wallet->db, stmt); - } - } -} - -bool wallet_invoices_load(struct wallet *wallet, struct invoices *invs) -{ - struct invoice *i; - int count = 0; - sqlite3_stmt *stmt = db_query(__func__, wallet->db, - "SELECT id, state, payment_key, payment_hash, " - "label, msatoshi, expiry_time, pay_index, " - "msatoshi_received " - "FROM invoices;"); - if (!stmt) { - log_broken(wallet->log, "Could not load invoices"); - return false; - } - - while (sqlite3_step(stmt) == SQLITE_ROW) { - i = tal(invs, struct invoice); - wallet_stmt2invoice(stmt, i); - invoice_add(invs, i); - count++; - } - - log_debug(wallet->log, "Loaded %d invoices from DB", count); - sqlite3_finalize(stmt); - return true; -} - -bool wallet_invoice_remove(struct wallet *wallet, struct invoice *inv) -{ - sqlite3_stmt *stmt = db_prepare(wallet->db, "DELETE FROM invoices WHERE id=?"); - sqlite3_bind_int64(stmt, 1, inv->id); - db_exec_prepared(wallet->db, stmt); - return sqlite3_changes(wallet->db->sql) == 1; -} struct htlc_stub *wallet_htlc_stubs(const tal_t *ctx, struct wallet *wallet, struct wallet_channel *chan) diff --git a/wallet/wallet.h b/wallet/wallet.h index f3cb8bacf..bd3eacf4f 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -14,6 +14,7 @@ #include #include +struct invoices; struct lightningd; struct pubkey; @@ -21,6 +22,7 @@ struct wallet { struct db *db; struct log *log; struct ext_key *bip32_base; + struct invoices *invoices; }; /* Possible states for tracked outputs in the database. Not sure yet @@ -343,57 +345,176 @@ bool wallet_htlcs_reconnect(struct wallet *wallet, struct htlc_in_map *htlcs_in, struct htlc_out_map *htlcs_out); -/** - * wallet_invoice_nextpaid -- Find a paid invoice. - * - * Get the first paid invoice greater than the given pay_index. Return NULL - * if no paid invoice found. The first ever paid invoice will - * have a pay_index of 1 or greater, so giving a pay_index of 0 will get - * the first ever paid invoice if there is one. - * - * @ctx: Context to create the returned label. - * @wallet: Wallet to query - * @pay_index: The paid invoice returned will have pay_index greater - * than this argument. - */ -struct invoice *wallet_invoice_nextpaid(const tal_t *ctx, - const struct wallet *wallet, - u64 pay_index); +/* /!\ This is a DB ENUM, please do not change the numbering of any + * already defined elements (adding is ok) /!\ */ +enum invoice_status { + UNPAID, + PAID, +}; + +struct invoice { + /* List off ld->wallet->invoices */ + 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; +}; + +#define INVOICE_MAX_LABEL_LEN 128 /** - * wallet_invoice_save -- Save/update an invoice to the wallet + * wallet_invoice_load - Load the invoices from the database * - * Save or update the invoice in the wallet. If `inv->id` is 0 this - * invoice will be considered a new invoice and result in an intert - * into the database, otherwise it'll be updated. + * @wallet - the wallet whose invoices are to be loaded. * - * @wallet: Wallet to store in - * @inv: Invoice to save + * All other wallet_invoice_* functions cannot be called + * until this function is called. + * As a databse operation it must be called within + * db_begin_transaction .. db_commit_transaction + * (all other invoice functions also have this requirement). + * Returns true if loaded successfully. */ -void wallet_invoice_save(struct wallet *wallet, struct invoice *inv); +bool wallet_invoice_load(struct wallet *wallet); /** - * wallet_invoices_load -- Load all invoices into memory + * wallet_invoice_create - Create a new invoice. * - * Load all invoices into the given `invoices` struct. + * @wallet - the wallet to create the invoice in. + * @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 * - * @wallet: Wallet to load invoices from - * @invs: invoices container to load into + * Returns NULL if label already exists or expiry is 0. + * FIXME: Fallback addresses */ -bool wallet_invoices_load(struct wallet *wallet, struct invoices *invs); +const struct invoice *wallet_invoice_create(struct wallet *wallet, + u64 *msatoshi TAKES, + const char *label TAKES, + u64 expiry); /** - * wallet_invoice_remove -- Remove the specified invoice from the wallet + * wallet_invoice_find_by_label - Search for an invoice by label * - * Remove the invoice from the underlying database. The invoice is - * identified by `inv->id` so if the caller does not have the full - * invoice, it may just instantiate a new one and set the `id` to - * match the desired invoice. + * @wallet - the wallet to search. + * @label - the label to search for. Must be null-terminated. * - * @wallet: Wallet to remove from - * @inv: Invoice to remove. + * Returns NULL if no invoice with that label exists. */ -bool wallet_invoice_remove(struct wallet *wallet, struct invoice *inv); +const struct invoice *wallet_invoice_find_by_label(struct wallet *wallet, + const char *label); + +/** + * wallet_invoice_find_unpaid - Search for an unpaid, unexpired invoice by + * payment_hash + * + * @wallet - the wallet to search. + * @rhash - the payment_hash to search for. + * + * Rerturns NULL if no invoice with that payment hash exists. + */ +const struct invoice *wallet_invoice_find_unpaid(struct wallet *wallet, + const struct sha256 *rhash); + +/** + * wallet_invoice_delete - Delete an invoice + * + * @wallet - the wallet to delete the invoice from. + * @invoice - the invoice to delete. + * + * Return false on failure. + */ +bool wallet_invoice_delete(struct wallet *wallet, + const struct invoice *invoice); + +/** + * wallet_invoice_iterate - Iterate over all existing invoices + * + * @wallet - the wallet whose invoices are to beiterated over. + * @invoice - the previous invoice you iterated over. + * + * Return NULL at end-of-sequence. Usage: + * + * const struct invoice *i; + * i = NULL; + * while ((i = wallet_invoice_iterate(wallet, i))) { + * ... + * } + */ +const struct invoice *wallet_invoice_iterate(struct wallet *wallet, + const struct invoice *invoice); + +/** + * wallet_invoice_resolve - Mark an invoice as paid + * + * @wallet - the wallet containing the invoice. + * @invoice - the invoice to mark as paid. + * @msatoshi_received - the actual amount received. + * + * Precondition: the invoice must not yet be expired (wallet + * does not check). + */ +void wallet_invoice_resolve(struct wallet *wallet, + const struct invoice *invoice, + u64 msatoshi_received); + +/** + * wallet_invoice_waitany - Wait for any invoice to be paid. + * + * @ctx - the owner of the callback. If the owner is freed, + * the callback is cancelled. + * @wallet - the wallet to query. + * @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 wallet_invoice_waitany(const tal_t *ctx, + struct wallet *wallet, + u64 lastpay_index, + void (*cb)(const struct invoice *, void*), + void *cbarg); + +/** + * wallet_invoice_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. + * @wallet - the wallet to query. + * @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 wallet_invoice_waitone(const tal_t *ctx, + struct wallet *wallet, + struct invoice const *invoice, + void (*cb)(const struct invoice *, void*), + void *cbarg); + /** * wallet_htlc_stubs - Retrieve HTLC stubs for the given channel