invoice: order by when they were paid.

We need some ordering to deliver them to the JSON "waitinvoice" command;
we use a counter where 0 means "unpaid".

We keep two lists now, one for unpaid and one for paid invoices.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2016-09-06 16:47:49 +09:30
parent 0761c12381
commit 27715f7732
7 changed files with 90 additions and 62 deletions

View File

@ -1088,23 +1088,22 @@ static void db_load_invoice(struct lightningd_state *dstate)
while ((err = sqlite3_step(stmt)) != SQLITE_DONE) {
struct rval r;
u64 msatoshi;
bool complete;
u64 msatoshi, paid_num;
const char *label;
if (err != SQLITE_ROW)
fatal("db_load_invoice:step gave %s:%s",
sqlite3_errstr(err),
sqlite3_errmsg(dstate->db->sql));
if (sqlite3_column_count(stmt) != 3)
fatal("db_load_pay:step gave %i cols, not 3",
if (sqlite3_column_count(stmt) != 4)
fatal("db_load_invoice:step gave %i cols, not 4",
sqlite3_column_count(stmt));
from_sql_blob(stmt, 0, &r, sizeof(r));
msatoshi = sqlite3_column_int64(stmt, 1);
label = (const char *)sqlite3_column_text(stmt, 2);
complete = sqlite3_column_int(stmt, 3);
invoice_add(dstate, &r, msatoshi, label, complete);
paid_num = sqlite3_column_int64(stmt, 3);
invoice_add(dstate, &r, msatoshi, label, paid_num);
}
tal_free(ctx);
}
@ -1199,8 +1198,8 @@ void db_init(struct lightningd_state *dstate)
"PRIMARY KEY(rhash)")
TABLE(invoice,
SQL_R(r), SQL_U64(msatoshi), SQL_INVLABEL(label),
SQL_BOOL(complete),
"PRIMARY KEY(r)")
SQL_U64(paid_num),
"PRIMARY KEY(label)")
TABLE(anchors,
SQL_PUBKEY(peer),
SQL_TXID(txid), SQL_U32(idx), SQL_U64(amount),
@ -1953,7 +1952,8 @@ bool db_new_invoice(struct lightningd_state *dstate,
return !errmsg;
}
bool db_resolve_invoice(struct lightningd_state *dstate, const struct rval *r)
bool db_resolve_invoice(struct lightningd_state *dstate,
const char *label, u64 paid_num)
{
const char *errmsg, *ctx = tal(dstate, char);
@ -1961,9 +1961,8 @@ bool db_resolve_invoice(struct lightningd_state *dstate, const struct rval *r)
assert(dstate->db->in_transaction);
errmsg = db_exec(ctx, dstate, "UPDATE invoice SET complete=%s WHERE r=x'%s';",
sql_bool(true),
tal_hexstr(ctx, r, sizeof(*r)));
errmsg = db_exec(ctx, dstate, "UPDATE invoice SET paid_num=%"PRIu64" WHERE label=x'%s';",
paid_num, tal_hexstr(ctx, label, strlen(label)));
if (errmsg)
log_broken(dstate->base_log, "%s:%s", __func__, errmsg);
tal_free(ctx);

View File

@ -55,7 +55,8 @@ bool db_update_htlc_state(struct peer *peer, const struct htlc *htlc,
enum htlc_state oldstate);
bool db_complete_pay_command(struct lightningd_state *dstate,
const struct htlc *htlc);
bool db_resolve_invoice(struct lightningd_state *dstate, const struct rval *r);
bool db_resolve_invoice(struct lightningd_state *dstate,
const char *label, u64 paid_num);
bool db_update_feechange_state(struct peer *peer,
const struct feechange *f,
enum htlc_state oldstate);

View File

@ -7,24 +7,36 @@
#include <ccan/tal/str/str.h>
#include <sodium/randombytes.h>
struct invoice *find_invoice(struct lightningd_state *dstate,
const struct sha256 *rhash)
static struct invoice *find_inv(const struct list_head *list,
const struct sha256 *rhash)
{
struct invoice *i;
list_for_each(&dstate->invoices, i, list) {
list_for_each(list, i, list) {
if (structeq(rhash, &i->rhash))
return i;
}
return NULL;
}
static struct invoice *find_invoice_by_label(struct lightningd_state *dstate,
struct invoice *find_unpaid(struct lightningd_state *dstate,
const struct sha256 *rhash)
{
return find_inv(&dstate->unpaid, rhash);
}
static struct invoice *find_paid(struct lightningd_state *dstate,
const struct sha256 *rhash)
{
return find_inv(&dstate->paid, rhash);
}
static struct invoice *find_invoice_by_label(const struct list_head *list,
const char *label)
{
struct invoice *i;
list_for_each(&dstate->invoices, i, list) {
list_for_each(list, i, list) {
if (streq(i->label, label))
return i;
}
@ -35,18 +47,33 @@ void invoice_add(struct lightningd_state *dstate,
const struct rval *r,
u64 msatoshi,
const char *label,
bool complete)
u64 paid_num)
{
struct invoice *invoice = tal(dstate, struct invoice);
invoice->msatoshi = msatoshi;
invoice->r = *r;
invoice->complete = complete;
invoice->paid_num = paid_num;
invoice->label = tal_strdup(invoice, label);
sha256(&invoice->rhash, invoice->r.r, sizeof(invoice->r.r));
list_add(&dstate->invoices, &invoice->list);
if (paid_num) {
list_add(&dstate->paid, &invoice->list);
if (paid_num > dstate->invoices_completed)
dstate->invoices_completed = paid_num;
} else
list_add(&dstate->unpaid, &invoice->list);
}
bool resolve_invoice(struct lightningd_state *dstate,
struct invoice *invoice)
{
invoice->paid_num = ++dstate->invoices_completed;
list_del_from(&dstate->unpaid, &invoice->list);
list_add_tail(&dstate->paid, &invoice->list);
return db_resolve_invoice(dstate, invoice->label, invoice->paid_num);
}
static void json_invoice(struct command *cmd,
const char *buffer, const jsmntok_t *params)
{
@ -75,7 +102,8 @@ static void json_invoice(struct command *cmd,
randombytes_buf(invoice->r.r, sizeof(invoice->r.r));
sha256(&invoice->rhash, invoice->r.r, sizeof(invoice->r.r));
if (find_invoice(cmd->dstate, &invoice->rhash)) {
if (find_unpaid(cmd->dstate, &invoice->rhash)
|| find_paid(cmd->dstate, &invoice->rhash)) {
command_fail(cmd, "Duplicate r value '%.*s'",
r->end - r->start, buffer + r->start);
return;
@ -91,7 +119,8 @@ static void json_invoice(struct command *cmd,
invoice->label = tal_strndup(invoice, buffer + label->start,
label->end - label->start);
if (find_invoice_by_label(cmd->dstate, invoice->label)) {
if (find_invoice_by_label(&cmd->dstate->paid, invoice->label)
|| find_invoice_by_label(&cmd->dstate->unpaid, invoice->label)) {
command_fail(cmd, "Duplicate label '%s'", invoice->label);
return;
}
@ -100,7 +129,7 @@ static void json_invoice(struct command *cmd,
INVOICE_MAX_LABEL_LEN);
return;
}
invoice->complete = false;
invoice->paid_num = 0;
if (!db_new_invoice(cmd->dstate, invoice->msatoshi, invoice->label,
&invoice->r)) {
@ -109,7 +138,7 @@ static void json_invoice(struct command *cmd,
}
/* OK, connect it to main state, respond with hash */
tal_steal(cmd->dstate, invoice);
list_add(&cmd->dstate->invoices, &invoice->list);
list_add(&cmd->dstate->unpaid, &invoice->list);
json_object_start(response, NULL);
json_add_hex(response, "rhash",
@ -126,10 +155,27 @@ const struct json_command invoice_command = {
"Returns the {rhash} on success. "
};
static void json_add_invoices(struct json_result *response,
const struct list_head *list,
const char *buffer, const jsmntok_t *label)
{
struct invoice *i;
list_for_each(list, i, list) {
if (label && !json_tok_streq(buffer, label, i->label))
continue;
json_object_start(response, NULL);
json_add_string(response, "label", i->label);
json_add_hex(response, "rhash", &i->rhash, sizeof(i->rhash));
json_add_u64(response, "msatoshi", i->msatoshi);
json_add_bool(response, "complete", i->paid_num != 0);
json_object_end(response);
}
}
static void json_listinvoice(struct command *cmd,
const char *buffer, const jsmntok_t *params)
{
struct invoice *i;
jsmntok_t *label = NULL;
struct json_result *response = new_json_result(cmd);
@ -143,16 +189,8 @@ static void json_listinvoice(struct command *cmd,
json_object_start(response, NULL);
json_array_start(response, NULL);
list_for_each(&cmd->dstate->invoices, i, list) {
if (label && !json_tok_streq(buffer, label, i->label))
continue;
json_object_start(response, NULL);
json_add_string(response, "label", i->label);
json_add_hex(response, "rhash", &i->rhash, sizeof(i->rhash));
json_add_u64(response, "msatoshi", i->msatoshi);
json_add_bool(response, "complete", i->complete);
json_object_end(response);
}
json_add_invoices(response, &cmd->dstate->paid, buffer, label);
json_add_invoices(response, &cmd->dstate->unpaid, buffer, label);
json_array_end(response);
json_object_end(response);
command_success(cmd, response);
@ -182,20 +220,16 @@ static void json_delinvoice(struct command *cmd,
label = tal_strndup(cmd, buffer + labeltok->start,
labeltok->end - labeltok->start);
i = find_invoice_by_label(cmd->dstate, label);
i = find_invoice_by_label(&cmd->dstate->unpaid, label);
if (!i) {
command_fail(cmd, "Unknown invoice");
return;
}
if (i->complete) {
command_fail(cmd, "Invoice already paid");
return;
}
if (!db_remove_invoice(cmd->dstate, i->label)) {
command_fail(cmd, "Database error");
return;
}
list_del_from(&cmd->dstate->invoices, &i->list);
list_del_from(&cmd->dstate->unpaid, &i->list);
json_object_start(response, NULL);
json_add_string(response, "label", i->label);

View File

@ -11,7 +11,7 @@ struct invoice {
u64 msatoshi;
struct rval r;
struct sha256 rhash;
bool complete;
u64 paid_num;
};
#define INVOICE_MAX_LABEL_LEN 128
@ -21,9 +21,12 @@ void invoice_add(struct lightningd_state *dstate,
const struct rval *r,
u64 msatoshi,
const char *label,
bool complete);
u64 complete);
struct invoice *find_invoice(struct lightningd_state *dstate,
const struct sha256 *rhash);
bool resolve_invoice(struct lightningd_state *dstate,
struct invoice *invoice);
struct invoice *find_unpaid(struct lightningd_state *dstate,
const struct sha256 *rhash);
#endif /* LIGHTNING_DAEMON_INVOICE_H */

View File

@ -255,7 +255,9 @@ static struct lightningd_state *lightningd_state(void)
default_config(&dstate->config);
list_head_init(&dstate->bitcoin_req);
list_head_init(&dstate->wallet);
list_head_init(&dstate->invoices);
list_head_init(&dstate->unpaid);
list_head_init(&dstate->paid);
dstate->invoices_completed = 0;
list_head_init(&dstate->addresses);
dstate->dev_never_routefail = false;
dstate->bitcoin_req_running = false;

View File

@ -111,7 +111,8 @@ struct lightningd_state {
struct list_head wallet;
/* Payments for r values we know about. */
struct list_head invoices;
struct list_head paid, unpaid;
u64 invoices_completed;
/* All known nodes. */
struct node_map *nodes;

View File

@ -535,7 +535,7 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc,
case ROUTE_STEP__NEXT_END:
if (only_dest)
return;
invoice = find_invoice(peer->dstate, &htlc->rhash);
invoice = find_unpaid(peer->dstate, &htlc->rhash);
if (!invoice) {
log_unusual(peer->log, "No invoice for HTLC %"PRIu64,
htlc->id);
@ -561,28 +561,16 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc,
return;
}
/* This is a courtesy: we could simply take your money! */
if (invoice->complete) {
log_unusual(peer->log,
"Repeated payment for '%s' HTLC %"PRIu64,
invoice->label, htlc->id);
command_htlc_set_fail(peer, htlc,
UNAUTHORIZED_401,
"already received payment");
return;
}
log_info(peer->log, "Immediately resolving '%s' HTLC %"PRIu64,
invoice->label, htlc->id);
if (!db_resolve_invoice(peer->dstate, &invoice->r)) {
if (!resolve_invoice(peer->dstate, invoice)) {
command_htlc_set_fail(peer, htlc,
INTERNAL_SERVER_ERROR_500,
"database error");
return;
}
invoice->complete = true;
set_htlc_rval(peer, htlc, &invoice->r);
command_htlc_fulfill(peer, htlc);
goto free_rest;