mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-26 20:30:59 +01:00
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 <rusty@rustcorp.com.au>
This commit is contained in:
parent
963f6b5d67
commit
3f4683e3f8
5 changed files with 166 additions and 8 deletions
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue