diff --git a/daemon/db.c b/daemon/db.c index 9076d3509..4bfda729b 100644 --- a/daemon/db.c +++ b/daemon/db.c @@ -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); diff --git a/daemon/db.h b/daemon/db.h index d67bc6387..ad78f819f 100644 --- a/daemon/db.h +++ b/daemon/db.h @@ -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); diff --git a/daemon/invoice.c b/daemon/invoice.c index 25a4e58e0..38650a3c7 100644 --- a/daemon/invoice.c +++ b/daemon/invoice.c @@ -7,24 +7,36 @@ #include #include -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); diff --git a/daemon/invoice.h b/daemon/invoice.h index 50fb22fb9..ac2ccb856 100644 --- a/daemon/invoice.h +++ b/daemon/invoice.h @@ -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 */ diff --git a/daemon/lightningd.c b/daemon/lightningd.c index 7be1bed4a..f9d03c43d 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -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; diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 7ef85ca60..5d24c5bc2 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -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; diff --git a/daemon/peer.c b/daemon/peer.c index aa936e27e..6825e35f8 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -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;