Browse Source

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>
ppa-0.6.1
Rusty Russell 8 years ago
parent
commit
27715f7732
  1. 23
      daemon/db.c
  2. 3
      daemon/db.h
  3. 90
      daemon/invoice.c
  4. 9
      daemon/invoice.h
  5. 4
      daemon/lightningd.c
  6. 3
      daemon/lightningd.h
  7. 16
      daemon/peer.c

23
daemon/db.c

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

3
daemon/db.h

@ -55,7 +55,8 @@ bool db_update_htlc_state(struct peer *peer, const struct htlc *htlc,
enum htlc_state oldstate); enum htlc_state oldstate);
bool db_complete_pay_command(struct lightningd_state *dstate, bool db_complete_pay_command(struct lightningd_state *dstate,
const struct htlc *htlc); 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, bool db_update_feechange_state(struct peer *peer,
const struct feechange *f, const struct feechange *f,
enum htlc_state oldstate); enum htlc_state oldstate);

90
daemon/invoice.c

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

9
daemon/invoice.h

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

4
daemon/lightningd.c

@ -255,7 +255,9 @@ static struct lightningd_state *lightningd_state(void)
default_config(&dstate->config); default_config(&dstate->config);
list_head_init(&dstate->bitcoin_req); list_head_init(&dstate->bitcoin_req);
list_head_init(&dstate->wallet); 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); list_head_init(&dstate->addresses);
dstate->dev_never_routefail = false; dstate->dev_never_routefail = false;
dstate->bitcoin_req_running = false; dstate->bitcoin_req_running = false;

3
daemon/lightningd.h

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

16
daemon/peer.c

@ -535,7 +535,7 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc,
case ROUTE_STEP__NEXT_END: case ROUTE_STEP__NEXT_END:
if (only_dest) if (only_dest)
return; return;
invoice = find_invoice(peer->dstate, &htlc->rhash); invoice = find_unpaid(peer->dstate, &htlc->rhash);
if (!invoice) { if (!invoice) {
log_unusual(peer->log, "No invoice for HTLC %"PRIu64, log_unusual(peer->log, "No invoice for HTLC %"PRIu64,
htlc->id); htlc->id);
@ -561,28 +561,16 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc,
return; 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, log_info(peer->log, "Immediately resolving '%s' HTLC %"PRIu64,
invoice->label, htlc->id); invoice->label, htlc->id);
if (!db_resolve_invoice(peer->dstate, &invoice->r)) { if (!resolve_invoice(peer->dstate, invoice)) {
command_htlc_set_fail(peer, htlc, command_htlc_set_fail(peer, htlc,
INTERNAL_SERVER_ERROR_500, INTERNAL_SERVER_ERROR_500,
"database error"); "database error");
return; return;
} }
invoice->complete = true;
set_htlc_rval(peer, htlc, &invoice->r); set_htlc_rval(peer, htlc, &invoice->r);
command_htlc_fulfill(peer, htlc); command_htlc_fulfill(peer, htlc);
goto free_rest; goto free_rest;

Loading…
Cancel
Save