From 3f4683e3f88ff6b6fa93163a6f77f56eaf726eab Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 14 Dec 2020 11:50:44 +1030 Subject: [PATCH] sendpay: optional argument to link local offer. This is for offers which have `send_invoice`: we need to associate the payment with the original offer, in (the usual) case where it is a single use offer. We mark it used when it's paid, to avoid a race. Signed-off-by: Rusty Russell --- common/jsonrpc_errors.h | 1 + lightningd/pay.c | 87 +++++++++++++++++++++++++++++++++++++++-- wallet/db.c | 2 + wallet/wallet.c | 74 +++++++++++++++++++++++++++++++++-- wallet/wallet.h | 10 +++++ 5 files changed, 166 insertions(+), 8 deletions(-) diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index a9c9eefd9..3058a5e12 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -41,6 +41,7 @@ static const errcode_t PAY_NO_SUCH_PAYMENT = 208; static const errcode_t PAY_UNSPECIFIED_ERROR = 209; static const errcode_t PAY_STOPPED_RETRYING = 210; static const errcode_t PAY_STATUS_UNEXPECTED = 211; +static const errcode_t PAY_OFFER_INVALID = 212; /* `fundchannel` or `withdraw` errors */ static const errcode_t FUND_MAX_EXCEEDED = 300; diff --git a/lightningd/pay.c b/lightningd/pay.c index c268f68bb..87dc46be6 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -334,6 +334,10 @@ void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, hout->partid); assert(payment); +#if EXPERIMENTAL_FEATURES + if (payment->local_offer_id) + wallet_offer_mark_used(ld->wallet->db, payment->local_offer_id); +#endif tell_waiters_success(ld, &hout->payment_hash, payment); } @@ -789,6 +793,60 @@ static const u8 *send_onion(const tal_t *ctx, struct lightningd *ld, &dont_care_about_channel_update); } +#if EXPERIMENTAL_FEATURES +static struct command_result *check_offer_usage(struct command *cmd, + const struct sha256 *local_offer_id) +{ + enum offer_status status; + const struct wallet_payment **payments; + + if (!local_offer_id) + return NULL; + + if (!wallet_offer_find(tmpctx, cmd->ld->wallet, local_offer_id, + NULL, &status)) + return command_fail(cmd, PAY_OFFER_INVALID, + "Unknown offer %s", + type_to_string(tmpctx, struct sha256, + local_offer_id)); + + if (!offer_status_active(status)) + return command_fail(cmd, PAY_OFFER_INVALID, + "Inactive offer %s", + type_to_string(tmpctx, struct sha256, + local_offer_id)); + + if (!offer_status_single(status)) + return NULL; + + /* OK, we must not attempt more than one payment at once for + * single_use offer */ + payments = wallet_payments_by_offer(tmpctx, cmd->ld->wallet, local_offer_id); + for (size_t i = 0; i < tal_count(payments); i++) { + switch (payments[i]->status) { + case PAYMENT_COMPLETE: + return command_fail(cmd, PAY_OFFER_INVALID, + "Single-use offer already paid" + " with %s", + type_to_string(tmpctx, struct sha256, + &payments[i] + ->payment_hash)); + case PAYMENT_PENDING: + return command_fail(cmd, PAY_OFFER_INVALID, + "Single-use offer already" + " in progress with %s", + type_to_string(tmpctx, struct sha256, + &payments[i] + ->payment_hash)); + case PAYMENT_FAILED: + break; + } + } + + return NULL; +} +#endif /* EXPERIMENTAL_FEATURES */ + /* destination/route_channels/route_nodes are NULL (and path_secrets may be NULL) * if we're sending a raw onion. */ static struct command_result * @@ -805,7 +863,8 @@ send_payment_core(struct lightningd *ld, const struct node_id *destination, struct node_id *route_nodes TAKES, struct short_channel_id *route_channels TAKES, - struct secret *path_secrets) + struct secret *path_secrets, + const struct sha256 *local_offer_id) { const struct wallet_payment **payments, *old_payment = NULL; struct channel *channel; @@ -942,6 +1001,13 @@ send_payment_core(struct lightningd *ld, &total_msat)); } +#if EXPERIMENTAL_FEATURES + struct command_result *offer_err; + offer_err = check_offer_usage(cmd, local_offer_id); + if (offer_err) + return offer_err; +#endif + channel = active_channel_by_id(ld, &first_hop->nodeid, NULL); if (!channel) { struct json_stream *data @@ -1016,6 +1082,10 @@ send_payment_core(struct lightningd *ld, payment->bolt11 = tal_strdup(payment, b11str); else payment->bolt11 = NULL; + if (local_offer_id) + payment->local_offer_id = tal_dup(payment, struct sha256, local_offer_id); + else + payment->local_offer_id = NULL; /* We write this into db when HTLC is actually sent. */ wallet_payment_setup(ld->wallet, payment); @@ -1034,6 +1104,7 @@ send_payment(struct lightningd *ld, struct amount_msat total_msat, const char *label TAKES, const char *b11str TAKES, + const struct sha256 *local_offer_id, const struct secret *payment_secret) { unsigned int base_expiry; @@ -1117,7 +1188,7 @@ send_payment(struct lightningd *ld, return send_payment_core(ld, cmd, rhash, partid, &route[0], msat, total_msat, label, b11str, packet, &ids[n_hops - 1], ids, - channels, path_secrets); + channels, path_secrets, local_offer_id); } static struct command_result * @@ -1202,6 +1273,7 @@ static struct command_result *json_sendonion(struct command *cmd, struct secret *path_secrets; struct amount_msat *msat; u64 *partid; + struct sha256 *local_offer_id = NULL; if (!param(cmd, buffer, params, p_req("onion", param_bin_from_hex, &onion), @@ -1213,6 +1285,9 @@ static struct command_result *json_sendonion(struct command *cmd, p_opt("bolt11", param_string, &b11str), p_opt_def("msatoshi", param_msat, &msat, AMOUNT_MSAT(0)), p_opt("destination", param_node_id, &destination), +#if EXPERIMENTAL_FEATURES + p_opt("local_offer_id", param_sha256, &local_offer_id), +#endif NULL)) return command_param_failed(); @@ -1227,7 +1302,7 @@ static struct command_result *json_sendonion(struct command *cmd, return send_payment_core(ld, cmd, payment_hash, *partid, first_hop, *msat, AMOUNT_MSAT(0), label, b11str, packet, destination, NULL, NULL, - path_secrets); + path_secrets, local_offer_id); } static const struct json_command sendonion_command = { @@ -1353,6 +1428,7 @@ static struct command_result *json_sendpay(struct command *cmd, const char *b11str, *label; u64 *partid; struct secret *payment_secret; + struct sha256 *local_offer_id = NULL; /* For generating help, give new-style. */ if (!param(cmd, buffer, params, @@ -1363,6 +1439,9 @@ static struct command_result *json_sendpay(struct command *cmd, p_opt("bolt11", param_string, &b11str), p_opt("payment_secret", param_secret, &payment_secret), p_opt_def("partid", param_u64, &partid, 0), +#if EXPERIMENTAL_FEATURES + p_opt("local_offer_id", param_sha256, &local_offer_id), +#endif NULL)) return command_param_failed(); @@ -1406,7 +1485,7 @@ static struct command_result *json_sendpay(struct command *cmd, route, final_amount, msat ? *msat : final_amount, - label, b11str, payment_secret); + label, b11str, local_offer_id, payment_secret); } static const struct json_command sendpay_command = { diff --git a/wallet/db.c b/wallet/db.c index 5a0b69002..4b891990c 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -666,6 +666,8 @@ static struct migration dbmigrations[] = { ");"), NULL}, /* A reference into our own offers table, if it was made from one */ {SQL("ALTER TABLE invoices ADD COLUMN local_offer_id BLOB DEFAULT NULL;"), NULL}, + /* A reference into our own offers table, if it was made from one */ + {SQL("ALTER TABLE payments ADD COLUMN local_offer_id BLOB DEFAULT NULL;"), NULL}, }; /* Leak tracking. */ diff --git a/wallet/wallet.c b/wallet/wallet.c index ccfdf12b4..a0ed72b7f 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2494,8 +2494,9 @@ void wallet_payment_store(struct wallet *wallet, " description," " bolt11," " total_msat," - " partid" - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); + " partid," + " local_offer_id" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); db_bind_int(stmt, 0, payment->status); db_bind_sha256(stmt, 1, &payment->payment_hash); @@ -2537,6 +2538,11 @@ void wallet_payment_store(struct wallet *wallet, db_bind_amount_msat(stmt, 11, &payment->total_msat); db_bind_u64(stmt, 12, payment->partid); + if (payment->local_offer_id != NULL) + db_bind_sha256(stmt, 13, payment->local_offer_id); + else + db_bind_null(stmt, 13); + db_exec_prepared_v2(stmt); payment->id = db_last_insert_id_v2(stmt); assert(payment->id > 0); @@ -2657,6 +2663,12 @@ static struct wallet_payment *wallet_stmt2payment(const tal_t *ctx, else payment->partid = 0; + if (!db_column_is_null(stmt, 16)) { + payment->local_offer_id = tal(payment, struct sha256); + db_column_sha256(stmt, 16, payment->local_offer_id); + } else + payment->local_offer_id = NULL; + return payment; } @@ -2690,6 +2702,7 @@ wallet_payment_by_hash(const tal_t *ctx, struct wallet *wallet, ", failonionreply" ", total_msat" ", partid" + ", local_offer_id" " FROM payments" " WHERE payment_hash = ?" " AND partid = ?")); @@ -2917,6 +2930,7 @@ wallet_payment_list(const tal_t *ctx, ", failonionreply" ", total_msat" ", partid" + ", local_offer_id" " FROM payments" " WHERE payment_hash = ?;")); db_bind_sha256(stmt, 0, payment_hash); @@ -2938,6 +2952,7 @@ wallet_payment_list(const tal_t *ctx, ", failonionreply" ", total_msat" ", partid" + ", local_offer_id" " FROM payments" " ORDER BY id;")); } @@ -2960,6 +2975,57 @@ wallet_payment_list(const tal_t *ctx, return payments; } +const struct wallet_payment ** +wallet_payments_by_offer(const tal_t *ctx, + struct wallet *wallet, + const struct sha256 *local_offer_id) +{ + const struct wallet_payment **payments; + struct db_stmt *stmt; + struct wallet_payment *p; + size_t i; + + payments = tal_arr(ctx, const struct wallet_payment *, 0); + stmt = db_prepare_v2(wallet->db, SQL("SELECT" + " id" + ", status" + ", destination" + ", msatoshi" + ", payment_hash" + ", timestamp" + ", payment_preimage" + ", path_secrets" + ", route_nodes" + ", route_channels" + ", msatoshi_sent" + ", description" + ", bolt11" + ", failonionreply" + ", total_msat" + ", partid" + ", local_offer_id" + " FROM payments" + " WHERE local_offer_id = ?;")); + db_bind_sha256(stmt, 0, local_offer_id); + db_query_prepared(stmt); + + for (i = 0; db_step(stmt); i++) { + tal_resize(&payments, i+1); + payments[i] = wallet_stmt2payment(payments, stmt); + } + tal_free(stmt); + + /* Now attach payments not yet in db. */ + list_for_each(&wallet->unstored_payments, p, list) { + if (!p->local_offer_id || !sha256_eq(p->local_offer_id, local_offer_id)) + continue; + tal_resize(&payments, i+1); + payments[i++] = p; + } + + return payments; +} + void wallet_htlc_sigs_save(struct wallet *w, u64 channel_id, const struct bitcoin_signature *htlc_sigs) { @@ -4083,8 +4149,8 @@ static void offer_status_update(struct db *db, stmt = db_prepare_v2(db, SQL("UPDATE invoices" " SET state=?" " WHERE state=? AND local_offer_id = ?;")); - db_bind_int(stmt, 0, invoice_status_in_db(UNPAID)); - db_bind_int(stmt, 1, invoice_status_in_db(EXPIRED)); + db_bind_int(stmt, 0, invoice_status_in_db(EXPIRED)); + db_bind_int(stmt, 1, invoice_status_in_db(UNPAID)); db_bind_sha256(stmt, 2, offer_id); db_exec_prepared_v2(take(stmt)); } diff --git a/wallet/wallet.h b/wallet/wallet.h index 8af718dfd..0f80b55b3 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -244,6 +244,9 @@ struct wallet_payment { /* If we could not decode the fail onion, just add it here. */ const u8 *failonion; + + /* If we are associated with an internal offer */ + struct sha256 *local_offer_id; }; struct outpoint { @@ -1078,6 +1081,13 @@ const struct wallet_payment **wallet_payment_list(const tal_t *ctx, struct wallet *wallet, const struct sha256 *payment_hash); +/** + * wallet_payments_by_offer - Retrieve a list of payments for this local_offer_id + */ +const struct wallet_payment **wallet_payments_by_offer(const tal_t *ctx, + struct wallet *wallet, + const struct sha256 *local_offer_id); + /** * wallet_htlc_sigs_save - Store the latest HTLC sigs for the channel */