Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xpay blinded path fix #8121

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions devtools/bolt12-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,6 @@ static bool print_blindedpaths(const char *fieldname,
struct blinded_path **paths,
struct blinded_payinfo **blindedpay)
{
size_t bp_idx = 0;

for (size_t i = 0; i < tal_count(paths); i++) {
struct blinded_path_hop **p = paths[i]->path;
printf("%s %zu/%zu: first_path_key %s ",
Expand All @@ -282,22 +280,24 @@ static bool print_blindedpaths(const char *fieldname,
fmt_pubkey(tmpctx,
&p[j]->blinded_node_id),
tal_hex(tmpctx, p[j]->encrypted_recipient_data));
if (blindedpay) {
if (bp_idx < tal_count(blindedpay))
printf("fee=%u/%u,cltv=%u,features=%s",
blindedpay[bp_idx]->fee_base_msat,
blindedpay[bp_idx]->fee_proportional_millionths,
blindedpay[bp_idx]->cltv_expiry_delta,
tal_hex(tmpctx,
blindedpay[bp_idx]->features));
bp_idx++;
}
}
if (i < tal_count(blindedpay)) {
printf(" blindedpay: fee=%u/%u,cltv=%u,features=%s",
blindedpay[i]->fee_base_msat,
blindedpay[i]->fee_proportional_millionths,
blindedpay[i]->cltv_expiry_delta,
tal_hex(tmpctx, blindedpay[i]->features));
}
printf("\n");
}
if (blindedpay && tal_count(blindedpay) != bp_idx) {
/* BOLT #12:
* - MUST reject the invoice if `invoice_blindedpay` does not
* contain exactly one `blinded_payinfo` per
* `invoice_paths`.`blinded_path`.
*/
if (blindedpay && tal_count(blindedpay) != tal_count(paths)) {
fprintf(stderr, "Expected %zu blindedpay fields, got %zu\n",
bp_idx, tal_count(blindedpay));
tal_count(paths), tal_count(blindedpay));
return false;
}
return true;
Expand Down Expand Up @@ -893,7 +893,11 @@ int main(int argc, char *argv[])
well_formed &= print_offer_amount(invreq->offer_chains,
invreq->offer_currency,
*invreq->offer_amount);
if (must_have(invreq, offer_description))
if (invreq->offer_amount && !invreq->offer_description) {
fprintf(stderr, "Missing offer_description (with offer_amount)\n");
well_formed = false;
}
if (invreq->offer_description)
well_formed &= print_utf8("offer_description", invreq->offer_description);
if (invreq->offer_features)
print_features("offer_features", invreq->offer_features);
Expand Down Expand Up @@ -971,7 +975,11 @@ int main(int argc, char *argv[])
well_formed &= print_offer_amount(invoice->offer_chains,
invoice->offer_currency,
*invoice->offer_amount);
if (must_have(invoice, offer_description))
if (invoice->offer_amount && !invoice->offer_description) {
fprintf(stderr, "Missing offer_description (with offer_amount)\n");
well_formed = false;
}
if (invoice->offer_description)
well_formed &= print_utf8("offer_description", invoice->offer_description);
if (invoice->offer_features)
print_features("offer_features", invoice->offer_features);
Expand Down
10 changes: 4 additions & 6 deletions plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ static void append_blinded_payloads(struct sphinx_path *sp,
size_t path_num)
{
const struct blinded_path *path = attempt->payment->paths[path_num];
u32 final_cltv = attempt->payment->final_cltv + effective_bheight;
u32 final_cltv = effective_bheight;

for (size_t i = 0; i < tal_count(path->path); i++) {
bool first = (i == 0);
Expand Down Expand Up @@ -1635,12 +1635,10 @@ static struct command_result *json_xpay_core(struct command *cmd,
"Could not resolve any paths: unknown short_channel_id");
}

/* Use worst-case CLTV. */
/* We don't actually know the final_cltv for blinded
* paths, we just know the cltv we use to enter the
* final hop. */
payment->final_cltv = 0;
for (size_t i = 0; i < tal_count(payment->payinfos); i++) {
if (payment->payinfos[i]->cltv_expiry_delta > payment->final_cltv)
payment->final_cltv = payment->payinfos[i]->cltv_expiry_delta;
}
/* We will start honoring this flag in future */
payment->disable_mpp = !feature_offered(b12inv->invoice_features, OPT_BASIC_MPP);
if (payment->disable_mpp && command_deprecated_in_ok(cmd, "ignore_bolt12_mpp", "v25.05", "v25.11"))
Expand Down
10 changes: 10 additions & 0 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4680,3 +4680,13 @@ def test_listaddresses(node_factory):
# start and limit from future
addresses = l1.rpc.listaddresses(start=21, limit=5)["addresses"]
assert len(addresses) == 0


def test_bolt12_invoice_decode(node_factory):
"""Test decode of a real invoice."""
l1 = node_factory.get_node()

inv = 'lni1qqg26r8checx54jang4393z59wa6293pqvvhnlnvurnfanndnxjtcjnmxrkj92xtsupa6lwjm7hkr8s8zflqk5sz82v9gqzcyypsvsgehgxpnlapwnahwt89fjrad8nzlxn0z0dmn46gqpk2qd2n8mdql5q0uqh34ry8vpl5zhy0ytqqtycqya6eg802fzrfec3sj6hj0vx0mnqtdypsz43dexx9tyt8ak270h957cedaw952ryqjzwgmzuvunqv53878sqzqg2upz426juplphy68fqqafavzzqm6msnsnsehgjmsnqhv39v7v3cqzraklvv0rl4sg654t44ujvetklp6urayt9vjprjgy35paec0a373khaj9r6cqg5x6u4qqvg24eqj3nn8gpfx3tv0075g5mmz6k6jezhnx6wh6s9atydz30ektzmhexua6ayuzuq53mayp8d5h8yhfdf373kzyzvecuqqep0zy8qljenhelz0awkws6p4llvg5tgcty6ev53l6pmgeqd5zmqgryr5wm968uchxmwr2k86qqtyymgze2y8qqqqqqqqqqq86qpgsqqqqqqqqqq05qqqqqqpq3qqqqqqq2gpr8hhtq82pqcel324ms8wd6vwphtm33l883xmh7hm2dwruc4v95xvn85kq46rw25q36nzhqxqsqqzczzqce08lxec8xnm8xmxdyh398kv8dy25vhpcrm47a9ha0vx0qwyn7p0cyqkm8qvweh5akjqjhlys34ys6gmm25jxrk3syearzr33qfk5czq8dxu04qq8njp0l3f0n6cuvyphqhtcjnaqg05vrgnhwzzmgm825383s'

assert l1.rpc.decode(inv)['valid'] is True
subprocess.run(["devtools/bolt12-cli", "decode", inv], check=True)
47 changes: 47 additions & 0 deletions tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,3 +766,50 @@ def test_fail_after_success(node_factory, bitcoind, executor, slow_mode):
'amount_sent_msat': res['amount_sent_msat'],
'failed_parts': 1,
'successful_parts': 1}


def test_xpay_twohop_bug(node_factory):
"""From https://github.com/ElementsProject/lightning/issues/8119:
Oh, interesting! I tried again and got a two-hop blinded path. xpay returned the same error you saw while pay was successful.

lightning-cli xpay lni1qqgv5nalmz08ukj4av074kyk6pepq93pqvvhnlnvurnfanndnxjtcjnmxrkj92xtsupa6lwjm7hkr8s8zflqk5sz82v9gqzcyypu03lyetn3ayp8p5798mz4der4xexkxxxu8ck0m25gmjaaj3n5scaql5q0uquxfmcztl0gldv8mxy3sm8x5jscdz27u39fy6luxu8zcdn9j73l3upaypvngk89elemj7cvu9v57r28k65e4jtlsr0vd66yzwrw2uzyvjczq0sd8fk6vazvrxvnks7hdqkl3lar4ff4a2ccjpltacz8z6tw6lunvqzrlrvyu3rkyylgd0splzr0xs3cccmzmfllyu7k06gclf9wx5n463z5arlwz2frk9a6lnfrvjdh3znsppxc4v8ahdy7e0y3us5rww0lcdxqj6psx87tgwvm3u260gc25frw9c4t368cecy3f5flll87tgk2uva09mncqqe9qqq0vdm023df0eetknvgcxk5yuupg8j5a5jtdmpj0u5unp8f3g2gskpma8l53sd5vmsfcrlr4rm7y9n2y8qqqqp7sqqqq8espgsqqqqqqqqqqqqqqqqqqqqq73qqqqq2gpr8hefjt2pqdgsfqcr3r5kcckf6sgtmkwuds8wp5uc7j0zcj0d9lsgl47vl95y25q36nzhqxqsqqzczzqce08lxec8xnm8xmxdyh398kv8dy25vhpcrm47a9ha0vx0qwyn7p0cyqt925k662c9kelq545l944k3j9gdvmkfm2ev2pqpnslmx8qvuwx3jqg4u8ful39aq0tlzujjd2yagjndcd2q42r5hvydx0lxe58mdrgs
{
"code": 209,
"message": "Failed after 1 attempts. We got an error from inside the blinded path 0x0x0/1: we assume it means insufficient capacity. Then routing failed: We could not find a usable set of paths. The shortest path is 858132x1647x1->861005x2291x1->0x0x0, but 0x0x0/1 layer xpay-0 says max is 14999msat"
}
lightning-cli pay lni1qqgv5nalmz08ukj4av074kyk6pepq93pqvvhnlnvurnfanndnxjtcjnmxrkj92xtsupa6lwjm7hkr8s8zflqk5sz82v9gqzcyypu03lyetn3ayp8p5798mz4der4xexkxxxu8ck0m25gmjaaj3n5scaql5q0uquxfmcztl0gldv8mxy3sm8x5jscdz27u39fy6luxu8zcdn9j73l3upaypvngk89elemj7cvu9v57r28k65e4jtlsr0vd66yzwrw2uzyvjczq0sd8fk6vazvrxvnks7hdqkl3lar4ff4a2ccjpltacz8z6tw6lunvqzrlrvyu3rkyylgd0splzr0xs3cccmzmfllyu7k06gclf9wx5n463z5arlwz2frk9a6lnfrvjdh3znsppxc4v8ahdy7e0y3us5rww0lcdxqj6psx87tgwvm3u260gc25frw9c4t368cecy3f5flll87tgk2uva09mncqqe9qqq0vdm023df0eetknvgcxk5yuupg8j5a5jtdmpj0u5unp8f3g2gskpma8l53sd5vmsfcrlr4rm7y9n2y8qqqqp7sqqqq8espgsqqqqqqqqqqqqqqqqqqqqq73qqqqq2gpr8hefjt2pqdgsfqcr3r5kcckf6sgtmkwuds8wp5uc7j0zcj0d9lsgl47vl95y25q36nzhqxqsqqzczzqce08lxec8xnm8xmxdyh398kv8dy25vhpcrm47a9ha0vx0qwyn7p0cyqt925k662c9kelq545l944k3j9gdvmkfm2ev2pqpnslmx8qvuwx3jqg4u8ful39aq0tlzujjd2yagjndcd2q42r5hvydx0lxe58mdrgs
{
"destination": "031979fe6ce0e69ece6d99a4bc4a7b30ed22a8cb8703dd7dd2dfaf619e07127e0b",
"payment_hash": "6a209060711d2d8c593a8217bb3b8d81dc1a731e93c5893da5fc11faf99f2d08",
"created_at": 1740526413.632475833,
"parts": 1,
"amount_msat": 16007,
"amount_sent_msat": 16010,
"payment_preimage": "c6c75b93dbbdbc5082350a4396afec0232ff4400ea0c9f053b977f4389f501bf",
"status": "complete"
}
"""
l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True,
opts=[{'cltv-delta': 50},
{'cltv-delta': 100},
{'cltv-delta': 200},
{'cltv-final': 400}])

offer = l4.rpc.offer('any')
inv = l1.rpc.fetchinvoice(offer['bolt12'], '15000msat')['invoice']

# Inserts a blinded path
path = only_one(l1.rpc.decode(inv)['invoice_paths'])
assert path['first_node_id'] == l3.info['id']
assert len(path['path']) == 2
assert path['payinfo']['cltv_expiry_delta'] == 200 + 400

# This works.
l1.rpc.pay(inv)
# CLTV is blockheight (108) + 1 + 100 + 200 + 400
l1.daemon.wait_for_log(f'Adding HTLC 0 amount=15002msat cltv={108 + 1 + 100 + 200 + 400}')

inv = l1.rpc.fetchinvoice(offer['bolt12'], '15000msat')['invoice']
# This doesn't!
l1.rpc.xpay(inv)
l1.daemon.wait_for_log(f'Adding HTLC 1 amount=15002msat cltv={108 + 1 + 100 + 200 + 400}')
Loading