mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
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:
parent
0761c12381
commit
27715f7732
23
daemon/db.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);
|
||||||
|
@ -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);
|
||||||
|
@ -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,18 +47,33 @@ 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,
|
||||||
const char *buffer, const jsmntok_t *params)
|
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));
|
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);
|
||||||
|
@ -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,
|
||||||
const struct sha256 *rhash);
|
struct invoice *invoice);
|
||||||
|
|
||||||
|
struct invoice *find_unpaid(struct lightningd_state *dstate,
|
||||||
|
const struct sha256 *rhash);
|
||||||
|
|
||||||
#endif /* LIGHTNING_DAEMON_INVOICE_H */
|
#endif /* LIGHTNING_DAEMON_INVOICE_H */
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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…
Reference in New Issue
Block a user