diff --git a/daemon/Makefile b/daemon/Makefile index 87eebd2d4..a9d13b5b8 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -25,6 +25,7 @@ DAEMON_SRC := \ daemon/failure.c \ daemon/feechange.c \ daemon/htlc.c \ + daemon/invoice.c \ daemon/jsonrpc.c \ daemon/lightningd.c \ daemon/netaddr.c \ @@ -33,7 +34,6 @@ DAEMON_SRC := \ daemon/output_to_htlc.c \ daemon/packets.c \ daemon/pay.c \ - daemon/payment.c \ daemon/peer.c \ daemon/routing.c \ daemon/secrets.c \ @@ -70,6 +70,7 @@ DAEMON_HEADERS := \ daemon/feechange_state.h \ daemon/htlc.h \ daemon/htlc_state.h \ + daemon/invoice.h \ daemon/json.h \ daemon/jsonrpc.h \ daemon/lightningd.h \ @@ -80,7 +81,6 @@ DAEMON_HEADERS := \ daemon/output_to_htlc.h \ daemon/packets.h \ daemon/pay.h \ - daemon/payment.h \ daemon/peer.h \ daemon/pseudorand.h \ daemon/routing.h \ diff --git a/daemon/db.c b/daemon/db.c index 1c9f239b0..93cac2531 100644 --- a/daemon/db.c +++ b/daemon/db.c @@ -3,12 +3,12 @@ #include "db.h" #include "feechange.h" #include "htlc.h" +#include "invoice.h" #include "lightningd.h" #include "log.h" #include "names.h" #include "netaddr.h" #include "pay.h" -#include "payment.h" #include "routing.h" #include "secrets.h" #include "utils.h" @@ -57,12 +57,13 @@ static const char *sqlite3_column_str(sqlite3_stmt *stmt, int iCol) #define SQL_RHASH(var) stringify(var)" CHAR(32)" #define SQL_SHA256(var) stringify(var)" CHAR(32)" #define SQL_R(var) stringify(var)" CHAR(32)" -#define SQL_STATENAME(var) stringify(var)" VARCHAR(44)" /* STATE_OPEN_WAITING_THEIRANCHOR_THEYCOMPLETED == 44*/ +#define SQL_STATENAME(var) stringify(var)" VARCHAR(44)" +#define SQL_INVLABEL(var) stringify(var)" VARCHAR("stringify(INVOICE_MAX_LABEL_LEN)")" /* 8 + 4 + (8 + 32) * (64 + 1) */ #define SHACHAIN_SIZE 2612 -#define SQL_SHACHAIN(var) stringify(var)" CHAR(2612)" +#define SQL_SHACHAIN(var) stringify(var)" CHAR("stringify(SHACHAIN_SIZE)")" /* FIXME: Should be fixed size. */ #define SQL_ROUTING(var) stringify(var)" BLOB" @@ -1072,26 +1073,27 @@ static void db_load_pay(struct lightningd_state *dstate) tal_free(ctx); } -static void db_load_payment(struct lightningd_state *dstate) +static void db_load_invoice(struct lightningd_state *dstate) { int err; sqlite3_stmt *stmt; char *ctx = tal(dstate, char); - err = sqlite3_prepare_v2(dstate->db->sql, "SELECT * FROM payment;", -1, + err = sqlite3_prepare_v2(dstate->db->sql, "SELECT * FROM invoice;", -1, &stmt, NULL); if (err != SQLITE_OK) - fatal("db_load_payment:prepare gave %s:%s", + fatal("db_load_invoice:prepare gave %s:%s", sqlite3_errstr(err), sqlite3_errmsg(dstate->db->sql)); while ((err = sqlite3_step(stmt)) != SQLITE_DONE) { struct rval r; u64 msatoshis; bool complete; + const char *label; if (err != SQLITE_ROW) - fatal("db_load_payment:step gave %s:%s", + fatal("db_load_invoice:step gave %s:%s", sqlite3_errstr(err), sqlite3_errmsg(dstate->db->sql)); if (sqlite3_column_count(stmt) != 3) @@ -1100,8 +1102,9 @@ static void db_load_payment(struct lightningd_state *dstate) from_sql_blob(stmt, 0, &r, sizeof(r)); msatoshis = sqlite3_column_int64(stmt, 1); - complete = sqlite3_column_int(stmt, 2); - payment_add(dstate, &r, msatoshis, complete); + label = (const char *)sqlite3_column_text(stmt, 2); + complete = sqlite3_column_int(stmt, 3); + invoice_add(dstate, &r, msatoshis, label, complete); } tal_free(ctx); } @@ -1147,7 +1150,7 @@ static void db_load(struct lightningd_state *dstate) db_load_addresses(dstate); db_load_peers(dstate); db_load_pay(dstate); - db_load_payment(dstate); + db_load_invoice(dstate); } void db_init(struct lightningd_state *dstate) @@ -1194,8 +1197,9 @@ void db_init(struct lightningd_state *dstate) SQL_BLOB(ids), SQL_PUBKEY(htlc_peer), SQL_U64(htlc_id), SQL_R(r), SQL_FAIL(fail), "PRIMARY KEY(rhash)") - TABLE(payment, - SQL_R(r), SQL_U64(msatoshis), SQL_BOOL(complete), + TABLE(invoice, + SQL_R(r), SQL_U64(msatoshis), SQL_INVLABEL(label), + SQL_BOOL(complete), "PRIMARY KEY(r)") TABLE(anchors, SQL_PUBKEY(peer), @@ -1926,8 +1930,9 @@ bool db_complete_pay_command(struct lightningd_state *dstate, return !errmsg; } -bool db_new_payment(struct lightningd_state *dstate, +bool db_new_invoice(struct lightningd_state *dstate, u64 msatoshis, + const char *label, const struct rval *r) { const char *errmsg, *ctx = tal(dstate, char); @@ -1935,10 +1940,12 @@ bool db_new_payment(struct lightningd_state *dstate, log_debug(dstate->base_log, "%s", __func__); assert(!dstate->db->in_transaction); - - errmsg = db_exec(ctx, dstate, "INSERT INTO payment VALUES (x'%s', %"PRIu64", %s);", + + /* Insert label as hex; suspect injection attacks. */ + errmsg = db_exec(ctx, dstate, "INSERT INTO invoice VALUES (x'%s', %"PRIu64", x'%s', %s);", tal_hexstr(ctx, r, sizeof(*r)), msatoshis, + tal_hexstr(ctx, label, strlen(label)), sql_bool(false)); if (errmsg) log_broken(dstate->base_log, "%s:%s", __func__, errmsg); @@ -1946,7 +1953,7 @@ bool db_new_payment(struct lightningd_state *dstate, return !errmsg; } -bool db_resolve_payment(struct lightningd_state *dstate, const struct rval *r) +bool db_resolve_invoice(struct lightningd_state *dstate, const struct rval *r) { const char *errmsg, *ctx = tal(dstate, char); @@ -1954,7 +1961,7 @@ bool db_resolve_payment(struct lightningd_state *dstate, const struct rval *r) assert(dstate->db->in_transaction); - errmsg = db_exec(ctx, dstate, "UPDATE payment SET complete=%s WHERE r=x'%s';", + errmsg = db_exec(ctx, dstate, "UPDATE invoice SET complete=%s WHERE r=x'%s';", sql_bool(true), tal_hexstr(ctx, r, sizeof(*r))); if (errmsg) diff --git a/daemon/db.h b/daemon/db.h index a97d2d630..2fce6ccc9 100644 --- a/daemon/db.h +++ b/daemon/db.h @@ -37,8 +37,9 @@ bool db_replace_pay_command(struct lightningd_state *dstate, const struct pubkey *ids, u64 msatoshis, const struct htlc *htlc); -bool db_new_payment(struct lightningd_state *dstate, +bool db_new_invoice(struct lightningd_state *dstate, u64 msatoshis, + const char *label, const struct rval *r); /* FIXME: save error handling until db_commit_transaction for calls @@ -51,7 +52,7 @@ 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_payment(struct lightningd_state *dstate, const struct rval *r); +bool db_resolve_invoice(struct lightningd_state *dstate, const struct rval *r); 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 new file mode 100644 index 000000000..716bb62f8 --- /dev/null +++ b/daemon/invoice.c @@ -0,0 +1,127 @@ +#include "db.h" +#include "invoice.h" +#include "jsonrpc.h" +#include "lightningd.h" +#include +#include +#include +#include + +struct invoice *find_invoice(struct lightningd_state *dstate, + const struct sha256 *rhash) +{ + struct invoice *i; + + list_for_each(&dstate->invoices, i, list) { + if (structeq(rhash, &i->rhash)) + return i; + } + return NULL; +} + +static struct invoice *find_invoice_by_label(struct lightningd_state *dstate, + const char *label) +{ + struct invoice *i; + + list_for_each(&dstate->invoices, i, list) { + if (streq(i->label, label)) + return i; + } + return NULL; +} + +void invoice_add(struct lightningd_state *dstate, + const struct rval *r, + u64 msatoshis, + const char *label, + bool complete) +{ + struct invoice *invoice = tal(dstate, struct invoice); + + invoice->msatoshis = msatoshis; + invoice->r = *r; + invoice->complete = complete; + invoice->label = tal_strdup(invoice, label); + sha256(&invoice->rhash, invoice->r.r, sizeof(invoice->r.r)); + list_add(&dstate->invoices, &invoice->list); +} + +static void json_invoice(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct invoice *invoice; + jsmntok_t *msatoshis, *r, *label; + struct json_result *response = new_json_result(cmd); + + if (!json_get_params(buffer, params, + "amount", &msatoshis, + "label", &label, + "?r", &r, + NULL)) { + command_fail(cmd, "Need {amount} and {label}"); + return; + } + + invoice = tal(cmd, struct invoice); + if (r) { + if (!hex_decode(buffer + r->start, r->end - r->start, + invoice->r.r, sizeof(invoice->r.r))) { + command_fail(cmd, "Invalid hex r '%.*s'", + r->end - r->start, buffer + r->start); + return; + } + } else + 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)) { + command_fail(cmd, "Duplicate r value '%.*s'", + r->end - r->start, buffer + r->start); + return; + } + + if (!json_tok_u64(buffer, msatoshis, &invoice->msatoshis) + || invoice->msatoshis == 0) { + command_fail(cmd, "'%.*s' is not a valid positive number", + msatoshis->end - msatoshis->start, + buffer + msatoshis->start); + return; + } + + invoice->label = tal_strndup(invoice, buffer + label->start, + label->end - label->start); + if (find_invoice_by_label(cmd->dstate, invoice->label)) { + command_fail(cmd, "Duplicate label '%s'", invoice->label); + return; + } + if (strlen(invoice->label) > INVOICE_MAX_LABEL_LEN) { + command_fail(cmd, "label '%s' over %u bytes", invoice->label, + INVOICE_MAX_LABEL_LEN); + return; + } + invoice->complete = false; + + if (!db_new_invoice(cmd->dstate, invoice->msatoshis, invoice->label, + &invoice->r)) { + command_fail(cmd, "database error"); + return; + } + /* OK, connect it to main state, respond with hash */ + tal_steal(cmd->dstate, invoice); + list_add(&cmd->dstate->invoices, &invoice->list); + + json_object_start(response, NULL); + json_add_hex(response, "rhash", + &invoice->rhash, sizeof(invoice->rhash)); + json_object_end(response); + + command_success(cmd, response); +} + +const struct json_command invoice_command = { + "invoice", + json_invoice, + "Create invoice for {msatoshis} with {label} (with a set {r}, otherwise generate one)", + "Returns the {rhash} on success. " +}; diff --git a/daemon/invoice.h b/daemon/invoice.h new file mode 100644 index 000000000..495d3a669 --- /dev/null +++ b/daemon/invoice.h @@ -0,0 +1,29 @@ +#ifndef LIGHTNING_DAEMON_INVOICE_H +#define LIGHTNING_DAEMON_INVOICE_H +#include "config.h" +#include "peer.h" + +struct lightningd_state; + +struct invoice { + struct list_node list; + const char *label; + u64 msatoshis; + struct rval r; + struct sha256 rhash; + bool complete; +}; + +#define INVOICE_MAX_LABEL_LEN 128 + +/* From database */ +void invoice_add(struct lightningd_state *dstate, + const struct rval *r, + u64 msatoshis, + const char *label, + bool complete); + +struct invoice *find_invoice(struct lightningd_state *dstate, + const struct sha256 *rhash); + +#endif /* LIGHTNING_DAEMON_INVOICE_H */ diff --git a/daemon/jsonrpc.c b/daemon/jsonrpc.c index 87c6746a8..3ee34171c 100644 --- a/daemon/jsonrpc.c +++ b/daemon/jsonrpc.c @@ -289,7 +289,7 @@ static const struct json_command *cmdlist[] = { &commit_command, &close_command, &newaddr_command, - &accept_payment_command, + &invoice_command, &getroute_command, &sendpay_command, &feerate_command, diff --git a/daemon/jsonrpc.h b/daemon/jsonrpc.h index 1ccd00631..96fe85aba 100644 --- a/daemon/jsonrpc.h +++ b/daemon/jsonrpc.h @@ -71,7 +71,7 @@ extern const struct json_command reconnect_command; extern const struct json_command disconnect_command; extern const struct json_command signcommit_command; extern const struct json_command output_command; -extern const struct json_command accept_payment_command; +extern const struct json_command invoice_command; extern const struct json_command add_route_command; extern const struct json_command routefail_command; extern const struct json_command getroute_command; diff --git a/daemon/lightningd.c b/daemon/lightningd.c index 4f00d3b48..7be1bed4a 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -255,7 +255,7 @@ 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->payments); + list_head_init(&dstate->invoices); 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 8d1c5c171..7ef85ca60 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -111,7 +111,7 @@ struct lightningd_state { struct list_head wallet; /* Payments for r values we know about. */ - struct list_head payments; + struct list_head invoices; /* All known nodes. */ struct node_map *nodes; diff --git a/daemon/payment.c b/daemon/payment.c deleted file mode 100644 index 4b88d9275..000000000 --- a/daemon/payment.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "db.h" -#include "jsonrpc.h" -#include "lightningd.h" -#include "payment.h" -#include -#include -#include - -struct payment *find_payment(struct lightningd_state *dstate, - const struct sha256 *rhash) -{ - struct payment *i; - - list_for_each(&dstate->payments, i, list) { - if (structeq(rhash, &i->rhash)) - return i; - } - return NULL; -} - -void payment_add(struct lightningd_state *dstate, - const struct rval *r, - u64 msatoshis, - bool complete) -{ - struct payment *payment = tal(dstate, struct payment); - - payment->msatoshis = msatoshis; - payment->r = *r; - payment->complete = complete; - sha256(&payment->rhash, payment->r.r, sizeof(payment->r.r)); - list_add(&dstate->payments, &payment->list); -} - -static void json_accept_payment(struct command *cmd, - const char *buffer, const jsmntok_t *params) -{ - struct payment *payment; - jsmntok_t *msatoshis, *r; - struct json_result *response = new_json_result(cmd); - - if (!json_get_params(buffer, params, - "amount", &msatoshis, - "?r", &r, - NULL)) { - command_fail(cmd, "Need {amount}"); - return; - } - - payment = tal(cmd, struct payment); - if (r) { - if (!hex_decode(buffer + r->start, r->end - r->start, - payment->r.r, sizeof(payment->r.r))) { - command_fail(cmd, "Invalid hex r '%.*s'", - r->end - r->start, buffer + r->start); - return; - } - } else - randombytes_buf(payment->r.r, sizeof(payment->r.r)); - - sha256(&payment->rhash, payment->r.r, sizeof(payment->r.r)); - if (find_payment(cmd->dstate, &payment->rhash)) { - command_fail(cmd, "Duplicate r value '%.*s'", - r->end - r->start, buffer + r->start); - return; - } - - if (!json_tok_u64(buffer, msatoshis, &payment->msatoshis) - || payment->msatoshis == 0) { - command_fail(cmd, "'%.*s' is not a valid positive number", - msatoshis->end - msatoshis->start, - buffer + msatoshis->start); - return; - } - payment->complete = false; - - if (!db_new_payment(cmd->dstate, payment->msatoshis, &payment->r)) { - command_fail(cmd, "database error"); - return; - } - /* OK, connect it to main state, respond with hash */ - tal_steal(cmd->dstate, payment); - list_add(&cmd->dstate->payments, &payment->list); - - json_object_start(response, NULL); - json_add_hex(response, "rhash", - &payment->rhash, sizeof(payment->rhash)); - json_object_end(response); - - command_success(cmd, response); -} - -const struct json_command accept_payment_command = { - "accept-payment", - json_accept_payment, - "Accept payment for {amount} (with a set {r}, otherwise generate one)", - "Returns the {rhash} on success. " -}; diff --git a/daemon/payment.h b/daemon/payment.h deleted file mode 100644 index f11b2bc7c..000000000 --- a/daemon/payment.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef LIGHTNING_DAEMON_PAYMENT_H -#define LIGHTNING_DAEMON_PAYMENT_H -#include "config.h" -#include "peer.h" - -struct lightningd_state; - -struct payment { - struct list_node list; - u64 msatoshis; - struct rval r; - struct sha256 rhash; - bool complete; -}; - -/* From database */ -void payment_add(struct lightningd_state *dstate, - const struct rval *r, - u64 msatoshis, - bool complete); - -struct payment *find_payment(struct lightningd_state *dstate, - const struct sha256 *rhash); - -#endif /* LIGHTNING_DAEMON_PAYMENT_H */ diff --git a/daemon/peer.c b/daemon/peer.c index c4746b7fa..9b5d5dfaf 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -7,6 +7,7 @@ #include "db.h" #include "dns.h" #include "find_p2sh_out.h" +#include "invoice.h" #include "jsonrpc.h" #include "lightningd.h" #include "log.h" @@ -16,7 +17,6 @@ #include "output_to_htlc.h" #include "packets.h" #include "pay.h" -#include "payment.h" #include "peer.h" #include "permute_tx.h" #include "protobuf_convert.h" @@ -492,7 +492,7 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, { RouteStep *step; const u8 *rest_of_route; - struct payment *payment; + struct invoice *invoice; if (abs_locktime_is_seconds(&htlc->expiry)) { log_unusual(peer->log, "HTLC %"PRIu64" is in seconds", htlc->id); @@ -535,9 +535,9 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, case ROUTE_STEP__NEXT_END: if (only_dest) return; - payment = find_payment(peer->dstate, &htlc->rhash); - if (!payment) { - log_unusual(peer->log, "No payment for HTLC %"PRIu64, + invoice = find_invoice(peer->dstate, &htlc->rhash); + if (!invoice) { + log_unusual(peer->log, "No invoice for HTLC %"PRIu64, htlc->id); log_add_struct(peer->log, " rhash=%s", struct sha256, &htlc->rhash); @@ -548,12 +548,13 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, goto free_rest; } - if (htlc->msatoshis != payment->msatoshis) { - log_unusual(peer->log, "Short payment for HTLC %"PRIu64 + if (htlc->msatoshis != invoice->msatoshis) { + log_unusual(peer->log, "Short payment for '%s' HTLC %"PRIu64 ": %"PRIu64" not %"PRIu64 " satoshi!", + invoice->label, htlc->id, htlc->msatoshis, - payment->msatoshis); + invoice->msatoshis); command_htlc_set_fail(peer, htlc, UNAUTHORIZED_401, "incorrect amount"); @@ -561,28 +562,28 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, } /* This is a courtesy: we could simply take your money! */ - if (payment->complete) { + if (invoice->complete) { log_unusual(peer->log, - "Repeated payment for HTLC %"PRIu64, - htlc->id); + "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 HTLC %"PRIu64, - htlc->id); + log_info(peer->log, "Immediately resolving '%s' HTLC %"PRIu64, + invoice->label, htlc->id); - if (!db_resolve_payment(peer->dstate, &payment->r)) { + if (!db_resolve_invoice(peer->dstate, &invoice->r)) { command_htlc_set_fail(peer, htlc, INTERNAL_SERVER_ERROR_500, "database error"); return; } - payment->complete = true; - set_htlc_rval(peer, htlc, &payment->r); + invoice->complete = true; + set_htlc_rval(peer, htlc, &invoice->r); command_htlc_fulfill(peer, htlc); goto free_rest; diff --git a/daemon/test/test.sh b/daemon/test/test.sh index b5f71dbdf..0cd515aac 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -959,7 +959,7 @@ check_status $A_AMOUNT $A_FEE "" $B_AMOUNT $B_FEE "" # Now, use automatic payment redemption lcli1 dev-routefail true lcli2 dev-routefail true -RHASH3=`lcli2 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` +RHASH3=`lcli2 invoice $HTLC_AMOUNT RHASH3 | sed 's/.*"\([0-9a-f]*\)".*/\1/'` HTLCID3=`lcli1 newhtlc $ID2 $HTLC_AMOUNT $EXPIRY $RHASH3 | extract_id` [ ! -n "$MANUALCOMMIT" ] || lcli1 commit $ID2 @@ -974,7 +974,7 @@ B_AMOUNT=$(($B_AMOUNT + $HTLC_AMOUNT)) check_status $A_AMOUNT $A_FEE "" $B_AMOUNT $B_FEE "" # Now, failed payment (didn't pay enough) -RHASH4=`lcli2 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` +RHASH4=`lcli2 invoice $HTLC_AMOUNT RHASH4 | sed 's/.*"\([0-9a-f]*\)".*/\1/'` # Shouldn't have this already. if lcli2 getlog | $FGREP 'Short payment for HTLC'; then exit 1; fi @@ -1010,7 +1010,7 @@ if [ ! -n "$MANUALCOMMIT" ]; then # Add to config in case we are restaring. echo "add-route=$ID2/$ID3/546000/10/36/36" >> $DIR1/config lcli1 add-route $ID2 $ID3 546000 10 36 36 - RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` + RHASH5=`lcli3 invoice $HTLC_AMOUNT RHASH5 | sed 's/.*"\([0-9a-f]*\)".*/\1/'` # Get route. ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT 1`