Skip to content

Commit

Permalink
bolt12: partial support for minimal offer
Browse files Browse the repository at this point in the history
Making sure that we are able to fetch the invoice
from an offer without description

E           pyln.client.lightning.RpcError: RPC call failed: method:
offer, payload: {'amount': '2msat'}, error: {'code': -32602, 'message':
'bolt12: Offer does not contain a description: invalid token
\'"lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyppvggz953rvg9rtxj8lalh43z8epwydjfrmffn3y3p5qz5cywpu09rr4vs"\''}

Link: #7405
Link: #7404
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
  • Loading branch information
vincenzopalazzo authored and rustyrussell committed Jul 11, 2024
1 parent 029034a commit 05f5976
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 459 deletions.
2 changes: 1 addition & 1 deletion cln-grpc/proto/node.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cln-grpc/src/convert.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cln-rpc/src/model.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions common/bolt12.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,16 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
}

/* BOLT-offers #12:
* - if `offer_description` is not set:
* - MUST NOT respond to the offer.
* - if `offer_node_id` is not set:
* - MUST NOT respond to the offer.
*
* - if offer_amount is set and offer_description is not set:
* - MUST NOT respond to the offer.
*/
if (!offer->offer_description) {
*fail = tal_strdup(ctx, "Offer does not contain a description");
if (!offer->offer_description && offer->offer_amount) {
*fail = tal_strdup(ctx, "Offer does not contain a description, but contains an amount");
return tal_free(offer);
}

/* FIXME(vincenzopalazzo): node id can be null when we use blinded path. */
if (!offer->offer_node_id) {
*fail = tal_strdup(ctx, "Offer does not contain a node_id");
return tal_free(offer);
Expand Down
3 changes: 1 addition & 2 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22996,8 +22996,7 @@
],
"request": {
"required": [
"amount",
"description"
"amount"
],
"properties": {
"amount": {
Expand Down
836 changes: 418 additions & 418 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions doc/schemas/lightning-offer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
],
"request": {
"required": [
"amount",
"description"
"amount"
],
"properties": {
"amount": {
Expand Down
13 changes: 6 additions & 7 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1827,16 +1827,15 @@ static struct command_result *json_createinvoice(struct command *cmd,
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Incorrect preimage");

if (!inv->offer_description)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Missing description in invoice");

if (command_check_only(cmd))
return command_check_done(cmd);

desc = tal_strndup(cmd,
inv->offer_description,
tal_bytelen(inv->offer_description));
if (inv->offer_description)
desc = tal_strndup(cmd,
inv->offer_description,
tal_bytelen(inv->offer_description));
else
desc = NULL;

if (!invoices_create(cmd->ld->wallet->invoices,
&inv_dbid,
Expand Down
12 changes: 5 additions & 7 deletions plugins/offers.c
Original file line number Diff line number Diff line change
Expand Up @@ -496,19 +496,17 @@ static bool json_add_offer_fields(struct json_stream *js,
} else if (offer_amount)
json_add_amount_msat(js, "offer_amount_msat",
amount_msat(*offer_amount));

/* BOLT-offers #12:
* A reader of an offer:
*...
* - if `offer_description` is not set:
* - MUST NOT respond to the offer.
*
* - if offer_amount is set and offer_description is not set:
* - MUST NOT respond to the offer.
*/
if (offer_description)
valid &= json_add_utf8(js, "offer_description",
offer_description);
else {
else if (!offer_description && offer_amount) {
json_add_string(js, "warning_missing_offer_description",
"offers without a description are invalid");
"description is required for the user to know what it was they paid for");
valid = false;
}

Expand Down
37 changes: 24 additions & 13 deletions plugins/offers_offer.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ struct command_result *json_offer(struct command *cmd,

if (!param(cmd, buffer, params,
p_req("amount", param_amount, offer),
p_req("description", param_escaped_string, &desc),
p_opt("description", param_escaped_string, &desc),
p_opt("issuer", param_escaped_string, &issuer),
p_opt("label", param_escaped_string, &offinfo->label),
p_opt("quantity_max", param_u64, &offer->offer_quantity_max),
Expand Down Expand Up @@ -357,12 +357,18 @@ struct command_result *json_offer(struct command *cmd,
"needs recurrence");
}

if (desc)
offer->offer_description
= tal_dup_arr(offer, char, desc, strlen(desc), 0);

/* BOLT-offers #12:
* - MUST set `offer_description` to a complete description of the
* purpose of the payment.
*
* - if offer_amount is set and offer_description is not set:
* - MUST NOT respond to the offer.
*/
offer->offer_description
= tal_dup_arr(offer, char, desc, strlen(desc), 0);
if (!offer->offer_description && offer->offer_amount)
return command_fail_badparam(cmd, "description", buffer, params,
"description is required for the user to know what it was they paid for");

/* BOLT-offers #12:
* - if it sets `offer_issuer`:
Expand Down Expand Up @@ -414,7 +420,7 @@ struct command_result *json_invoicerequest(struct command *cmd,

if (!param(cmd, buffer, params,
p_req("amount", param_msat, &msat),
p_req("description", param_escaped_string, &desc),
p_opt("description", param_escaped_string, &desc),
p_opt("issuer", param_escaped_string, &issuer),
p_opt("label", param_escaped_string, &label),
p_opt("absolute_expiry", param_u64,
Expand All @@ -429,15 +435,20 @@ struct command_result *json_invoicerequest(struct command *cmd,

/* BOLT-offers #12:
* - otherwise (not responding to an offer):
* - MUST set (or not set) `offer_description`, `offer_absolute_expiry`, `offer_paths` and `offer_issuer` as it would for an offer.
* - MUST set `invreq_payer_id` as it would set `offer_node_id` for an offer.
* - MUST NOT include `signature`, `offer_metadata`, `offer_chains`, `offer_amount`, `offer_currency`, `offer_features`, `offer_quantity_max` or `offer_node_id`
* - MUST set offer_description to a complete description of the purpose of the payment.
* - MUST set (or not set) offer_absolute_expiry and offer_issuer as it would for an offer.
* - MUST set invreq_payer_id (as it would set offer_node_id for an offer).
* - MUST set invreq_paths as it would set (or not set) offer_paths for an offer.
* - MUST NOT include signature, offer_metadata, offer_chains, offer_amount, offer_currency, offer_features, offer_quantity_max, offer_paths or offer_node_id
* - if the chain for the invoice is not solely bitcoin:
* - MUST specify `invreq_chain` the offer is valid for.
* - MUST set `invreq_amount`.
* - MUST pecify invreq_chain the offer is valid for.
* - MUST set invreq_amount.
*/
invreq->offer_description
= tal_dup_arr(invreq, char, desc, strlen(desc), 0);
if (desc)
invreq->offer_description = tal_dup_arr(invreq, char, desc, strlen(desc), 0);
else
invreq->offer_description = NULL;

if (issuer) {
invreq->offer_issuer
= tal_dup_arr(invreq, char, issuer, strlen(issuer), 0);
Expand Down
32 changes: 32 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -5721,3 +5721,35 @@ def test_onionmessage_ratelimit(node_factory):
# It will recover though!
time.sleep(0.250)
l1.rpc.fetchinvoice(offer['bolt12'])


def test_fetch_no_description_offer(node_factory):
"""Reproducing the issue: https://github.com/ElementsProject/lightning/issues/7405"""
l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None,
'allow-deprecated-apis': True})

# Deprecated fields make schema checker upset.
offer = l2.rpc.call('offer', {'amount': 'any'})
inv = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12'], 'amount_msat': '2sat'})

# Deprecated fields make schema checker upset.
l1.rpc.jsonschemas = {}
offer_decode = l1.rpc.decode(offer['bolt12'])
assert offer_decode['type'] == 'bolt12 offer', f'No possible to decode the offer `{offer}`'

l1.rpc.pay(inv['invoice'])


def test_fetch_no_description_with_amount(node_factory):
"""Reproducing the issue: https://github.com/ElementsProject/lightning/issues/7405"""
l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None,
'allow-deprecated-apis': True})

# Deprecated fields make schema checker upset.
# BOLT-offers #12:
#
# - if offer_amount is set and offer_description is not set:
# - MUST NOT respond to the offer.
err = r'description is required for the user to know what it was they paid for'
with pytest.raises(RpcError, match=err) as err:
_ = l2.rpc.call('offer', {'amount': '2msat'})

0 comments on commit 05f5976

Please sign in to comment.