db: add invoicerequests table.

We no longer use offers for "I want to send you money", but we'll use
invoice_requests directly.  Create a new table for them, and
associated functions.

The "localofferid" for "pay" and "sendpay" is now "localinvreqid".
This is an experimental-only option, so document the change under
experimental only.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-EXPERIMENTAL: JSON-RPC: `pay` and `sendpay` `localofferid` is now `localinvreqid`.
This commit is contained in:
Rusty Russell 2022-11-09 13:02:01 +10:30 committed by Christian Decker
parent 891cef7b2b
commit 02d7454226
20 changed files with 344 additions and 82 deletions

View File

@ -874,6 +874,7 @@
"Pay.exclude": 10,
"Pay.exemptfee": 7,
"Pay.label": 3,
"Pay.localinvreqid": 14,
"Pay.localofferid": 9,
"Pay.maxdelay": 6,
"Pay.maxfee": 11,
@ -913,6 +914,7 @@
"SendOnion.first_hop": 2,
"SendOnion.groupid": 11,
"SendOnion.label": 4,
"SendOnion.localinvreqid": 13,
"SendOnion.localofferid": 10,
"SendOnion.msatoshi": 8,
"SendOnion.onion": 1,
@ -940,6 +942,7 @@
"SendPay.bolt11": 5,
"SendPay.groupid": 9,
"SendPay.label": 3,
"SendPay.localinvreqid": 11,
"SendPay.localofferid": 8,
"SendPay.msatoshi": 4,
"SendPay.partid": 7,

Binary file not shown.

BIN
cln-grpc/src/convert.rs generated

Binary file not shown.

BIN
cln-rpc/src/model.rs generated

Binary file not shown.

View File

@ -45,7 +45,7 @@ enum jsonrpc_errcode {
PAY_UNSPECIFIED_ERROR = 209,
PAY_STOPPED_RETRYING = 210,
PAY_STATUS_UNEXPECTED = 211,
PAY_OFFER_INVALID = 212,
PAY_INVOICE_REQUEST_INVALID = 212,
/* `fundchannel` or `withdraw` errors */
FUND_MAX_EXCEEDED = 300,

View File

@ -1073,7 +1073,7 @@ class LightningRpc(UnixDomainSocketRpc):
def pay(self, bolt11, amount_msat=None, label=None, riskfactor=None,
maxfeepercent=None, retry_for=None,
maxdelay=None, exemptfee=None, localofferid=None, exclude=None,
maxdelay=None, exemptfee=None, localinvreqid=None, exclude=None,
maxfee=None, description=None, msatoshi=None):
"""
Send payment specified by {bolt11} with {amount_msat}
@ -1092,7 +1092,7 @@ class LightningRpc(UnixDomainSocketRpc):
"retry_for": retry_for,
"maxdelay": maxdelay,
"exemptfee": exemptfee,
"localofferid": localofferid,
"localinvreqid": localinvreqid,
"exclude": exclude,
"maxfee": maxfee,
"description": description,

Binary file not shown.

View File

@ -6,7 +6,7 @@ SYNOPSIS
**pay** *bolt11* [*msatoshi*] [*label*] [*riskfactor*]
[*maxfeepercent*] [*retry_for*] [*maxdelay*] [*exemptfee*]
[*localofferid*] [*exclude*] [*maxfee*] [*description*]
[*localinvreqid*] [*exclude*] [*maxfee*] [*description*]
DESCRIPTION
-----------
@ -32,8 +32,8 @@ leveraged by forwarding nodes. Setting `exemptfee` allows the
`maxfeepercent` check to be skipped on fees that are smaller than
`exemptfee` (default: 5000 millisatoshi).
`localofferid` is used by offers to link a payment attempt to a local
`send_invoice` offer created by lightningd-offerout(7). This ensures
`localinvreqid` is used by offers to link a payment attempt to a local
`invoice_request` offer created by lightningd-invoicerequest(7). This ensures
that we only make a single payment for an offer, and that the offer is
marked `used` once paid.

View File

@ -5,7 +5,7 @@ SYNOPSIS
--------
**sendpay** *route* *payment\_hash* [*label*] [*msatoshi*]
[*bolt11*] [*payment_secret*] [*partid*] [*localofferid*] [*groupid*]
[*bolt11*] [*payment_secret*] [*partid*] [*localinvreqid*] [*groupid*]
[*payment_metadata*] [*description*]
DESCRIPTION
@ -45,9 +45,9 @@ partial payments with the same *payment_hash*. The *msatoshi* amount
*payment_hash* must be equal, and **sendpay** will fail if there are
already *msatoshi* worth of payments pending.
The *localofferid* value indicates that this payment is being made for a local
send_invoice offer: this ensures that we only send a payment for a single-use
offer once.
The *localinvreqid* value indicates that this payment is being made for a local
invoice_request: this ensures that we only send a payment for a single-use
invoice_request once.
*groupid* allows you to attach a number which appears in **listsendpays** so
payments can be identified as part of a logical group. The *pay* plugin uses
@ -109,7 +109,7 @@ The following error codes may occur:
will be routing failure object.
- 204: Failure along route; retry a different route. The *data* field
of the error will be routing failure object.
- 212: *localofferid* refers to an invalid, or used, local offer.
- 212: *localinvreqid* refers to an invalid, or used, local invoice_request.
A routing failure object has the fields below:
- *erring\_index*. The index of the node along the route that reported

View File

@ -30,7 +30,7 @@
"exemptfee": {
"type": "msat"
},
"localofferid": {
"localinvreqid": {
"type": "hex"
},
"exclude": {

View File

@ -54,7 +54,7 @@
"destination": {
"type": "pubkey"
},
"localofferid": {
"localinvreqid": {
"type": "hash"
},
"groupid": {

View File

@ -54,7 +54,7 @@
"partid": {
"type": "u16"
},
"localofferid": {
"localinvreqid": {
"type": "hex"
},
"groupid": {

View File

@ -341,8 +341,9 @@ void payment_succeeded(struct lightningd *ld, struct htlc_out *hout,
hout->partid, hout->groupid);
assert(payment);
if (payment->local_offer_id)
wallet_offer_mark_used(ld->wallet->db, payment->local_offer_id);
if (payment->local_invreq_id)
wallet_invoice_request_mark_used(ld->wallet->db,
payment->local_invreq_id);
tell_waiters_success(ld, &hout->payment_hash, payment);
}
@ -777,46 +778,48 @@ static const u8 *send_onion(const tal_t *ctx, struct lightningd *ld,
blinding, partid, groupid, onion, NULL, hout);
}
static struct command_result *check_offer_usage(struct command *cmd,
const struct sha256 *local_offer_id)
static struct command_result *check_invoice_request_usage(struct command *cmd,
const struct sha256 *local_invreq_id)
{
enum offer_status status;
const struct wallet_payment **payments;
if (!local_offer_id)
if (!local_invreq_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",
if (!wallet_invoice_request_find(tmpctx, cmd->ld->wallet,
local_invreq_id,
NULL, &status))
return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID,
"Unknown invoice_request %s",
type_to_string(tmpctx, struct sha256,
local_offer_id));
local_invreq_id));
if (!offer_status_active(status))
return command_fail(cmd, PAY_OFFER_INVALID,
"Inactive offer %s",
return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID,
"Inactive invoice_request %s",
type_to_string(tmpctx, struct sha256,
local_offer_id));
local_invreq_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);
* single_use invoice_request we publish! */
payments = wallet_payments_by_invoice_request(tmpctx, cmd->ld->wallet,
local_invreq_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"
return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID,
"Single-use invoice_request 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"
return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID,
"Single-use invoice_request already"
" in progress with %s",
type_to_string(tmpctx, struct sha256,
&payments[i]
@ -872,7 +875,7 @@ send_payment_core(struct lightningd *ld,
struct node_id *route_nodes TAKES,
struct short_channel_id *route_channels TAKES,
struct secret *path_secrets,
const struct sha256 *local_offer_id)
const struct sha256 *local_invreq_id)
{
const struct wallet_payment **payments, *old_payment = NULL;
struct channel *channel;
@ -881,6 +884,7 @@ send_payment_core(struct lightningd *ld,
struct routing_failure *fail;
struct amount_msat msat_already_pending = AMOUNT_MSAT(0);
bool have_complete = false;
struct command_result *invreq_err;
/* Now, do we already have one or more payments? */
payments = wallet_payment_list(tmpctx, ld->wallet, rhash);
@ -1037,10 +1041,9 @@ send_payment_core(struct lightningd *ld,
&total_msat));
}
struct command_result *offer_err;
offer_err = check_offer_usage(cmd, local_offer_id);
if (offer_err)
return offer_err;
invreq_err = check_invoice_request_usage(cmd, local_invreq_id);
if (invreq_err)
return invreq_err;
channel = find_channel_for_htlc_add(ld, &first_hop->node_id,
&first_hop->scid);
@ -1117,8 +1120,8 @@ send_payment_core(struct lightningd *ld,
payment->description = tal_strdup(payment, description);
else
payment->description = NULL;
payment->local_offer_id = tal_dup_or_null(payment, struct sha256,
local_offer_id);
payment->local_invreq_id = tal_dup_or_null(payment, struct sha256,
local_invreq_id);
/* We write this into db when HTLC is actually sent. */
wallet_payment_setup(ld->wallet, payment);
@ -1139,7 +1142,7 @@ send_payment(struct lightningd *ld,
const char *label TAKES,
const char *invstring TAKES,
const char *description TAKES,
const struct sha256 *local_offer_id,
const struct sha256 *local_invreq_id,
const struct secret *payment_secret,
const u8 *payment_metadata)
{
@ -1212,7 +1215,7 @@ send_payment(struct lightningd *ld,
msat, total_msat,
label, invstring, description,
packet, &ids[n_hops - 1], ids,
channels, path_secrets, local_offer_id);
channels, path_secrets, local_invreq_id);
}
static struct command_result *
@ -1286,7 +1289,7 @@ static struct command_result *json_sendonion(struct command *cmd,
struct secret *path_secrets;
struct amount_msat *msat;
u64 *partid, *group;
struct sha256 *local_offer_id = NULL;
struct sha256 *local_invreq_id = NULL;
if (!param(cmd, buffer, params,
p_req("onion", param_bin_from_hex, &onion),
@ -1299,7 +1302,7 @@ static struct command_result *json_sendonion(struct command *cmd,
p_opt("bolt11", param_string, &invstring),
p_opt_def("amount_msat|msatoshi", param_msat, &msat, AMOUNT_MSAT(0)),
p_opt("destination", param_node_id, &destination),
p_opt("localofferid", param_sha256, &local_offer_id),
p_opt("localinvreqid", param_sha256, &local_invreq_id),
p_opt("groupid", param_u64, &group),
p_opt("description", param_string, &description),
NULL))
@ -1325,7 +1328,7 @@ static struct command_result *json_sendonion(struct command *cmd,
first_hop, *msat, AMOUNT_MSAT(0),
label, invstring, description,
packet, destination, NULL, NULL,
path_secrets, local_offer_id);
path_secrets, local_invreq_id);
}
static const struct json_command sendonion_command = {
@ -1412,7 +1415,7 @@ static struct command_result *json_sendpay(struct command *cmd,
const char *invstring, *label, *description;
u64 *partid, *group;
struct secret *payment_secret;
struct sha256 *local_offer_id;
struct sha256 *local_invreq_id;
u8 *payment_metadata;
/* For generating help, give new-style. */
@ -1425,7 +1428,7 @@ static struct command_result *json_sendpay(struct command *cmd,
p_opt("bolt11", param_string, &invstring),
p_opt("payment_secret", param_secret, &payment_secret),
p_opt_def("partid", param_u64, &partid, 0),
p_opt("localofferid", param_sha256, &local_offer_id),
p_opt("localinvreqid", param_sha256, &local_invreq_id),
p_opt("groupid", param_u64, &group),
p_opt("payment_metadata", param_bin_from_hex, &payment_metadata),
p_opt("description", param_string, &description),
@ -1478,7 +1481,7 @@ static struct command_result *json_sendpay(struct command *cmd,
route,
final_amount,
msat ? *msat : final_amount,
label, invstring, description, local_offer_id,
label, invstring, description, local_invreq_id,
payment_secret, payment_metadata);
}

View File

@ -92,7 +92,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
p->features = parent->features;
p->id = parent->id;
p->local_id = parent->local_id;
p->local_offer_id = parent->local_offer_id;
p->local_invreq_id = parent->local_invreq_id;
p->groupid = parent->groupid;
p->invstring = parent->invstring;
p->description = parent->description;
@ -107,7 +107,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
p->description = NULL;
/* Caller must set this. */
p->local_id = NULL;
p->local_offer_id = NULL;
p->local_invreq_id = NULL;
p->groupid = 0;
}
@ -1599,8 +1599,8 @@ static struct command_result *payment_createonion_success(struct command *cmd,
if (p->destination)
json_add_node_id(req->js, "destination", p->destination);
if (p->local_offer_id)
json_add_sha256(req->js, "localofferid", p->local_offer_id);
if (p->local_invreq_id)
json_add_sha256(req->js, "localinvreqid", p->local_invreq_id);
send_outreq(p->plugin, req);
return command_still_pending(cmd);

View File

@ -277,9 +277,9 @@ struct payment {
/* Description, usually set if bolt11 has only description_hash */
const char *description;
/* If this is paying a local offer, this is the one (sendpay ensures we
* don't pay twice for single-use offers) */
struct sha256 *local_offer_id;
/* If this is paying a local invoice_request, this is the one (sendpay
* ensures we don't pay twice for single-use invoice requests) */
struct sha256 *local_invreq_id;
/* Textual explanation of why this payment was attempted. */
const char *why;

View File

@ -987,7 +987,7 @@ static struct command_result *json_pay(struct command *cmd,
struct shadow_route_data *shadow_route;
struct amount_msat *invmsat;
u64 invexpiry;
struct sha256 *local_offer_id;
struct sha256 *local_invreq_id;
const struct tlv_invoice *b12;
struct out_req *req;
struct route_exclusion **exclusions;
@ -1011,7 +1011,7 @@ static struct command_result *json_pay(struct command *cmd,
p_opt_def("maxdelay", param_number, &maxdelay,
maxdelay_default),
p_opt("exemptfee", param_msat, &exemptfee),
p_opt("localofferid", param_sha256, &local_offer_id),
p_opt("localinvreqid", param_sha256, &local_invreq_id),
p_opt("exclude", param_route_exclusion_array, &exclusions),
p_opt("maxfee", param_msat, &maxfee),
p_opt("description", param_string, &description),
@ -1159,7 +1159,7 @@ static struct command_result *json_pay(struct command *cmd,
invexpiry = *b12->invoice_created_at + *b12->invoice_relative_expiry;
else
invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY;
p->local_offer_id = tal_steal(p, local_offer_id);
p->local_invreq_id = tal_steal(p, local_invreq_id);
}
if (time_now().ts.tv_sec > invexpiry)

View File

@ -416,7 +416,7 @@ def test_pay_plugin(node_factory):
# Make sure usage messages are present.
msg = 'pay bolt11 [amount_msat] [label] [riskfactor] [maxfeepercent] '\
'[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude] '\
'[retry_for] [maxdelay] [exemptfee] [localinvreqid] [exclude] '\
'[maxfee] [description]'
if DEVELOPER:
msg += ' [use_shadow]'

View File

@ -930,6 +930,16 @@ static struct migration dbmigrations[] = {
{SQL("ALTER TABLE channels ADD scid BIGINT;"), migrate_channels_scids_as_integers},
{SQL("ALTER TABLE payments ADD failscid BIGINT;"), migrate_payments_scids_as_integers},
{SQL("ALTER TABLE outputs ADD is_in_coinbase INTEGER DEFAULT 0;"), NULL},
{SQL("CREATE TABLE invoicerequests ("
" invreq_id BLOB"
", bolt12 TEXT"
", label TEXT"
", status INTEGER"
", PRIMARY KEY (invreq_id)"
");"), NULL},
/* A reference into our own invoicerequests table, if it was made from one */
{SQL("ALTER TABLE payments ADD COLUMN local_invreq_id BLOB DEFAULT NULL REFERENCES invoicerequests(invreq_id);"), NULL},
/* FIXME: Remove payments local_offer_id column! */
};
/* Released versions are of form v{num}[.{num}]* */

View File

@ -3094,7 +3094,7 @@ void wallet_payment_store(struct wallet *wallet,
" bolt11,"
" total_msat,"
" partid,"
" local_offer_id,"
" local_invreq_id,"
" groupid,"
" paydescription"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
@ -3139,8 +3139,8 @@ 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);
if (payment->local_invreq_id != NULL)
db_bind_sha256(stmt, 13, payment->local_invreq_id);
else
db_bind_null(stmt, 13);
@ -3285,11 +3285,11 @@ static struct wallet_payment *wallet_stmt2payment(const tal_t *ctx,
else
payment->partid = 0;
if (!db_col_is_null(stmt, "local_offer_id")) {
payment->local_offer_id = tal(payment, struct sha256);
db_col_sha256(stmt, "local_offer_id", payment->local_offer_id);
if (!db_col_is_null(stmt, "local_invreq_id")) {
payment->local_invreq_id = tal(payment, struct sha256);
db_col_sha256(stmt, "local_invreq_id", payment->local_invreq_id);
} else
payment->local_offer_id = NULL;
payment->local_invreq_id = NULL;
if (!db_col_is_null(stmt, "completed_at")) {
payment->completed_at = tal(payment, u32);
@ -3333,7 +3333,7 @@ wallet_payment_by_hash(const tal_t *ctx, struct wallet *wallet,
", failonionreply"
", total_msat"
", partid"
", local_offer_id"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
@ -3575,7 +3575,7 @@ wallet_payment_list(const tal_t *ctx,
", failonionreply"
", total_msat"
", partid"
", local_offer_id"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
@ -3602,7 +3602,7 @@ wallet_payment_list(const tal_t *ctx,
", failonionreply"
", total_msat"
", partid"
", local_offer_id"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
@ -3628,9 +3628,9 @@ wallet_payment_list(const tal_t *ctx,
}
const struct wallet_payment **
wallet_payments_by_offer(const tal_t *ctx,
struct wallet *wallet,
const struct sha256 *local_offer_id)
wallet_payments_by_invoice_request(const tal_t *ctx,
struct wallet *wallet,
const struct sha256 *local_invreq_id)
{
const struct wallet_payment **payments;
struct db_stmt *stmt;
@ -3656,12 +3656,12 @@ wallet_payments_by_offer(const tal_t *ctx,
", failonionreply"
", total_msat"
", partid"
", local_offer_id"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE local_offer_id = ?;"));
db_bind_sha256(stmt, 0, local_offer_id);
" WHERE local_invreq_id = ?;"));
db_bind_sha256(stmt, 0, local_invreq_id);
db_query_prepared(stmt);
for (i = 0; db_step(stmt); i++) {
@ -3672,7 +3672,7 @@ wallet_payments_by_offer(const tal_t *ctx,
/* 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))
if (!p->local_invreq_id || !sha256_eq(p->local_invreq_id, local_invreq_id))
continue;
tal_resize(&payments, i+1);
payments[i++] = p;
@ -5130,6 +5130,172 @@ void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id)
}
}
bool wallet_invoice_request_create(struct wallet *w,
const struct sha256 *invreq_id,
const char *bolt12,
const struct json_escape *label,
enum offer_status status)
{
struct db_stmt *stmt;
assert(offer_status_active(status));
/* Test if already exists. */
stmt = db_prepare_v2(w->db, SQL("SELECT 1"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, 0, invreq_id);
db_query_prepared(stmt);
if (db_step(stmt)) {
db_col_ignore(stmt, "1");
tal_free(stmt);
return false;
}
tal_free(stmt);
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO invoicerequests ("
" invreq_id"
", bolt12"
", label"
", status"
") VALUES (?, ?, ?, ?);"));
db_bind_sha256(stmt, 0, invreq_id);
db_bind_text(stmt, 1, bolt12);
if (label)
db_bind_json_escape(stmt, 2, label);
else
db_bind_null(stmt, 2);
db_bind_int(stmt, 3, offer_status_in_db(status));
db_exec_prepared_v2(take(stmt));
return true;
}
char *wallet_invoice_request_find(const tal_t *ctx,
struct wallet *w,
const struct sha256 *invreq_id,
const struct json_escape **label,
enum offer_status *status)
{
struct db_stmt *stmt;
char *bolt12;
stmt = db_prepare_v2(w->db, SQL("SELECT bolt12, label, status"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, 0, invreq_id);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
bolt12 = db_col_strdup(ctx, stmt, "bolt12");
if (label) {
if (db_col_is_null(stmt, "label"))
*label = NULL;
else
*label = db_col_json_escape(ctx, stmt, "label");
} else
db_col_ignore(stmt, "label");
if (status)
*status = offer_status_in_db(db_col_int(stmt, "status"));
else
db_col_ignore(stmt, "status");
tal_free(stmt);
return bolt12;
}
struct db_stmt *wallet_invreq_id_first(struct wallet *w, struct sha256 *invreq_id)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT invreq_id FROM invoicerequests;"));
db_query_prepared(stmt);
return wallet_invreq_id_next(w, stmt, invreq_id);
}
struct db_stmt *wallet_invreq_id_next(struct wallet *w,
struct db_stmt *stmt,
struct sha256 *invreq_id)
{
if (!db_step(stmt))
return tal_free(stmt);
db_col_sha256(stmt, "invreq_id", invreq_id);
return stmt;
}
/* If we make an invoice_request inactive */
static void invoice_request_status_update(struct db *db,
const struct sha256 *invreq_id,
enum offer_status oldstatus,
enum offer_status newstatus)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db, SQL("UPDATE invoicerequests"
" SET status=?"
" WHERE invreq_id = ?;"));
db_bind_int(stmt, 0, offer_status_in_db(newstatus));
db_bind_sha256(stmt, 1, invreq_id);
db_exec_prepared_v2(take(stmt));
}
enum offer_status wallet_invoice_request_disable(struct wallet *w,
const struct sha256 *invreq_id,
enum offer_status s)
{
enum offer_status newstatus;
assert(offer_status_active(s));
newstatus = offer_status_in_db(s & ~OFFER_STATUS_ACTIVE_F);
invoice_request_status_update(w->db, invreq_id, s, newstatus);
return newstatus;
}
void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq_id)
{
struct db_stmt *stmt;
enum offer_status status;
stmt = db_prepare_v2(db, SQL("SELECT status"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, 0, invreq_id);
db_query_prepared(stmt);
if (!db_step(stmt))
fatal("%s: unknown invreq_id %s",
__func__,
type_to_string(tmpctx, struct sha256, invreq_id));
status = offer_status_in_db(db_col_int(stmt, "status"));
tal_free(stmt);
if (!offer_status_active(status))
fatal("%s: invreq_id %s not active: status %i",
__func__,
type_to_string(tmpctx, struct sha256, invreq_id),
status);
if (!offer_status_used(status)) {
enum offer_status newstatus;
if (offer_status_single(status))
newstatus = OFFER_SINGLE_USE_USED;
else
newstatus = OFFER_MULTIPLE_USE_USED;
invoice_request_status_update(db, invreq_id, status, newstatus);
}
}
/* We join key parts with nuls for now. */
static void db_bind_datastore_key(struct db_stmt *stmt,

View File

@ -368,8 +368,8 @@ 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;
/* If we are associated with an internal invoice_request */
struct sha256 *local_invreq_id;
};
struct outpoint {
@ -1195,11 +1195,12 @@ const struct wallet_payment **wallet_payment_list(const tal_t *ctx,
/**
* wallet_payments_by_offer - Retrieve a list of payments for this local_offer_id
* wallet_payments_by_invoice_request - Retrieve a list of payments for this local_invreq_id
*/
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 **
wallet_payments_by_invoice_request(const tal_t *ctx,
struct wallet *wallet,
const struct sha256 *local_invreq_id);
/**
* wallet_htlc_sigs_save - Store the latest HTLC sigs for the channel
@ -1583,6 +1584,85 @@ enum offer_status wallet_offer_disable(struct wallet *w,
void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id)
NO_NULL_ARGS;
/**
* Store an offer in the database.
* @w: the wallet
* @invreq_id: the hash of the invoice_request.
* @bolt12: invoice_request as text.
* @label: optional label for this invoice_request.
* @status: OFFER_SINGLE_USE or OFFER_MULTIPLE_USE
*/
bool wallet_invoice_request_create(struct wallet *w,
const struct sha256 *invreq_id,
const char *bolt12,
const struct json_escape *label,
enum offer_status status)
NON_NULL_ARGS(1,2,3);
/**
* Retrieve an invoice_request from the database.
* @ctx: the tal context to allocate return from.
* @w: the wallet
* @invreq_id: the merkle root, as used for signing (must be unique)
* @label: the label of the invoice_request, set to NULL if none (or NULL)
* @status: set if succeeds (or NULL)
*
* If @invreq_id is found, returns the bolt12 text, sets @label and
* @state. Otherwise returns NULL.
*/
char *wallet_invoice_request_find(const tal_t *ctx,
struct wallet *w,
const struct sha256 *invreq_id,
const struct json_escape **label,
enum offer_status *status)
NON_NULL_ARGS(1,2,3);
/**
* Iterate through all the invoice_requests.
* @w: the wallet
* @invreq_id: the first invoice_request id (if returns non-NULL)
*
* Returns pointer to hand as @stmt to wallet_invreq_id_next(), or NULL.
* If you choose not to call wallet_invreq_id_next() you must free it!
*/
struct db_stmt *wallet_invreq_id_first(struct wallet *w,
struct sha256 *invreq_id);
/**
* Iterate through all the invoice_requests.
* @w: the wallet
* @stmt: return from wallet_invreq_id_first() or previous wallet_invreq_id_next()
* @invreq_id: the next invoice_request id (if returns non-NULL)
*
* Returns NULL once we're out of invoice_requests. If you choose not to call
* wallet_invreq_id_next() again you must free return.
*/
struct db_stmt *wallet_invreq_id_next(struct wallet *w,
struct db_stmt *stmt,
struct sha256 *invreq_id);
/**
* Disable an invoice_request in the database.
* @w: the wallet
* @invreq_id: the merkle root, as used for signing (must be unique)
* @s: the current status (must be active).
*
* Must exist. Returns new status. */
enum offer_status wallet_invoice_request_disable(struct wallet *w,
const struct sha256 *invreq_id,
enum offer_status s)
NO_NULL_ARGS;
/**
* Mark an invoice_request in the database used.
* @w: the wallet
* @invreq_id: the merkle root, as used for signing (must be unique)
*
* Must exist and be active.
*/
void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq_id)
NO_NULL_ARGS;
/**
* Add an new key/value to the datastore (generation 0)
* @w: the wallet