mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-17 19:03:42 +01:00
invoices: Extensive reorganization of invoice system.
This commit is contained in:
parent
ad5eb1f7e1
commit
93dc90990f
@ -18,62 +18,6 @@
|
||||
#include <sodium/randombytes.h>
|
||||
#include <wire/wire_sync.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,47 +6,4 @@
|
||||
#include <ccan/list/list.h>
|
||||
#include <ccan/tal/tal.h>
|
||||
|
||||
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 */
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include <gossipd/gen_gossip_wire.h>
|
||||
#include <lightningd/chaintopology.h>
|
||||
#include <lightningd/htlc_end.h>
|
||||
#include <lightningd/invoice.h>
|
||||
#include <lightningd/lightningd.h>
|
||||
#include <lightningd/log.h>
|
||||
#include <lightningd/pay.h>
|
||||
@ -20,6 +19,7 @@
|
||||
#include <lightningd/subd.h>
|
||||
#include <onchaind/gen_onchain_wire.h>
|
||||
#include <onchaind/onchain_wire.h>
|
||||
#include <wallet/wallet.h>
|
||||
#include <wire/gen_onion_wire.h>
|
||||
|
||||
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:
|
||||
|
@ -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(); }
|
||||
|
@ -6,6 +6,7 @@ wallet-wrongdir:
|
||||
|
||||
WALLET_LIB_SRC := \
|
||||
wallet/db.c \
|
||||
wallet/invoices.c \
|
||||
wallet/wallet.c \
|
||||
wallet/walletrpc.c
|
||||
|
||||
|
402
wallet/invoices.c
Normal file
402
wallet/invoices.c
Normal file
@ -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);
|
||||
}
|
160
wallet/invoices.h
Normal file
160
wallet/invoices.h
Normal file
@ -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 */
|
@ -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 \
|
||||
|
@ -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
|
||||
*
|
||||
|
249
wallet/wallet.c
249
wallet/wallet.c
@ -1,3 +1,4 @@
|
||||
#include "invoices.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <bitcoin/script.h>
|
||||
@ -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)
|
||||
|
193
wallet/wallet.h
193
wallet/wallet.h
@ -14,6 +14,7 @@
|
||||
#include <onchaind/onchain_wire.h>
|
||||
#include <wally_bip32.h>
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user