diff --git a/common/sphinx.c b/common/sphinx.c index 7ddc8e5638f9..815e85d3481b 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -1,6 +1,7 @@ #include "config.h" #include +#include #include #include #include @@ -122,6 +123,33 @@ bool sphinx_add_hop_has_length(struct sphinx_path *path, const struct pubkey *pu return true; } +static u8 *make_v0_hop(const tal_t *ctx, + const struct short_channel_id *scid, + struct amount_msat forward, u32 outgoing_cltv) +{ + const u8 padding[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + /* Prepend 0 byte for realm */ + u8 *buf = tal_arrz(ctx, u8, 1); + towire_short_channel_id(&buf, *scid); + towire_amount_msat(&buf, forward); + towire_u32(&buf, outgoing_cltv); + towire(&buf, padding, ARRAY_SIZE(padding)); + assert(tal_bytelen(buf) == 1 + 32); + return buf; +} + +void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const struct short_channel_id *scid, + struct amount_msat forward, u32 outgoing_cltv) +{ + struct sphinx_hop sp; + + sp.raw_payload = make_v0_hop(path, scid, forward, outgoing_cltv); + sp.pubkey = *pubkey; + tal_arr_expand(&path->hops, sp); +} + void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey, const u8 *payload TAKES) { @@ -639,12 +667,55 @@ struct route_step *process_onionpacket( /* Any of these could fail, falling thru with cursor == NULL */ payload_size = fromwire_bigsize(&cursor, &max); - /* FIXME: raw_payload *includes* the length, which is redundant and - * means we can't just ust fromwire_tal_arrn. */ - fromwire_pad(&cursor, &max, payload_size); - if (cursor != NULL) - step->raw_payload = tal_dup_arr(step, u8, paddedheader, - cursor - paddedheader, 0); + + /* Legacy! 0 length payload means fixed 32 byte structure */ + if (payload_size == 0 && max >= 32) { + struct tlv_payload *legacy = tlv_payload_new(tmpctx); + const u8 *legacy_cursor = cursor; + size_t legacy_max = 32; + u8 *onwire_tlv; + + legacy->amt_to_forward = tal(legacy, u64); + legacy->outgoing_cltv_value = tal(legacy, u32); + legacy->short_channel_id = tal(legacy, struct short_channel_id); + + /* BOLT-obsolete #4: + * ## Legacy `hop_data` payload format + * + * The `hop_data` format is identified by a single `0x00`-byte + * length, for backward compatibility. Its payload is defined + * as: + * + * 1. type: `hop_data` (for `realm` 0) + * 2. data: + * * [`short_channel_id`:`short_channel_id`] + * * [`u64`:`amt_to_forward`] + * * [`u32`:`outgoing_cltv_value`] + * * [`12*byte`:`padding`] + */ + *legacy->short_channel_id = fromwire_short_channel_id(&legacy_cursor, &legacy_max); + *legacy->amt_to_forward = fromwire_u64(&legacy_cursor, &legacy_max); + *legacy->outgoing_cltv_value = fromwire_u32(&legacy_cursor, &legacy_max); + + /* Re-linearize it as a modern TLV! */ + onwire_tlv = tal_arr(tmpctx, u8, 0); + towire_tlv_payload(&onwire_tlv, legacy); + + /* Length, then tlv */ + step->raw_payload = tal_arr(step, u8, 0); + towire_bigsize(&step->raw_payload, tal_bytelen(onwire_tlv)); + towire_u8_array(&step->raw_payload, onwire_tlv, tal_bytelen(onwire_tlv)); + + payload_size = 32; + fromwire_pad(&cursor, &max, payload_size); + } else { + /* FIXME: raw_payload *includes* the length, which is redundant and + * means we can't just ust fromwire_tal_arrn. */ + fromwire_pad(&cursor, &max, payload_size); + if (cursor != NULL) + step->raw_payload = tal_dup_arr(step, u8, paddedheader, + cursor - paddedheader, 0); + } fromwire_hmac(&cursor, &max, &step->next->hmac); /* BOLT #4: diff --git a/common/sphinx.h b/common/sphinx.h index bebde8eeab7b..da90e7558d50 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -216,6 +216,13 @@ bool sphinx_add_hop_has_length(struct sphinx_path *path, const struct pubkey *pu void sphinx_add_hop(struct sphinx_path *path, const struct pubkey *pubkey, const u8 *payload TAKES); +/** + * Do not use, function is cursed. + */ +void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const struct short_channel_id *scid, + struct amount_msat forward, u32 outgoing_cltv); + /** * Compute the size of the serialized payloads. */ diff --git a/common/test/run-sphinx-xor_cipher_stream.c b/common/test/run-sphinx-xor_cipher_stream.c index e09083f41e54..b5a30ef72699 100644 --- a/common/test/run-sphinx-xor_cipher_stream.c +++ b/common/test/run-sphinx-xor_cipher_stream.c @@ -108,9 +108,15 @@ void subkey_from_hmac(const char *prefix UNNEEDED, const struct secret *base UNNEEDED, struct secret *key UNNEEDED) { fprintf(stderr, "subkey_from_hmac called!\n"); abort(); } +/* Generated stub for tlv_payload_new */ +struct tlv_payload *tlv_payload_new(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "tlv_payload_new called!\n"); abort(); } /* Generated stub for towire */ void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) { fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_amount_msat */ +void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED) +{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); } /* Generated stub for towire_bigsize */ void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) { fprintf(stderr, "towire_bigsize called!\n"); abort(); } @@ -130,6 +136,9 @@ void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, /* Generated stub for towire_sha256 */ void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) { fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_tlv_payload */ +void towire_tlv_payload(u8 **pptr UNNEEDED, const struct tlv_payload *record UNNEEDED) +{ fprintf(stderr, "towire_tlv_payload called!\n"); abort(); } /* Generated stub for towire_u16 */ void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) { fprintf(stderr, "towire_u16 called!\n"); abort(); } diff --git a/lightningd/pay.c b/lightningd/pay.c index f04c28f2a368..f871399c6477 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -1150,7 +1150,8 @@ send_payment(struct lightningd *ld, const char *description TAKES, const struct sha256 *local_invreq_id, const struct secret *payment_secret, - const u8 *payment_metadata) + const u8 *payment_metadata, + bool dev_legacy_hop) { unsigned int base_expiry; struct onionpacket *packet; @@ -1177,6 +1178,14 @@ send_payment(struct lightningd *ld, ret = pubkey_from_node_id(&pubkey, &ids[i]); assert(ret); + if (dev_legacy_hop && i == n_hops - 2) { + sphinx_add_v0_hop(path, &pubkey, + &route[i + 1].scid, + route[i + 1].amount, + base_expiry + route[i + 1].delay); + continue; + } + sphinx_add_hop_has_length(path, &pubkey, take(onion_nonfinal_hop(NULL, &route[i + 1].scid, @@ -1506,6 +1515,7 @@ static struct command_result *json_sendpay(struct command *cmd, struct secret *payment_secret; struct sha256 *local_invreq_id; u8 *payment_metadata; + bool *dev_legacy_hop; if (!param_check(cmd, buffer, params, p_req("route", param_route_hops, &route), @@ -1520,6 +1530,7 @@ static struct command_result *json_sendpay(struct command *cmd, p_opt("groupid", param_u64, &group), p_opt("payment_metadata", param_bin_from_hex, &payment_metadata), p_opt("description", param_string, &description), + p_opt_dev("dev_legacy_hop", param_bool, &dev_legacy_hop, false), NULL)) return command_param_failed(); @@ -1582,7 +1593,7 @@ static struct command_result *json_sendpay(struct command *cmd, final_amount, msat ? *msat : final_amount, label, invstring, description, local_invreq_id, - payment_secret, payment_metadata); + payment_secret, payment_metadata, *dev_legacy_hop); } static const struct json_command sendpay_command = { diff --git a/tests/test_pay.py b/tests/test_pay.py index fd0bf70f94ae..558ed83b03db 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5661,3 +5661,31 @@ def test_pay_while_opening_channel(node_factory, bitcoind, executor): assert only_one(l1.rpc.listpeerchannels(l3.info['id'])['channels'])['state'] == 'OPENINGD' inv = l2.rpc.invoice(10000, "inv", "inv") l1.rpc.pay(inv['bolt11']) + + +def test_pay_legacy_forward(node_factory, bitcoind, executor): + """We removed legacy in 22.11, and LND will still send them for + route hints! See + https://github.com/lightningnetwork/lnd/issues/8785 + + """ + l1, l2, l3 = node_factory.line_graph(3, fundamount=10**6, wait_for_announce=True) + + inv = l3.rpc.invoice(1000, "inv", "inv") + + chanid12 = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['short_channel_id'] + chanid23 = only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['short_channel_id'] + route = [{'amount_msat': 1011, + 'id': l2.info['id'], + 'delay': 20, + 'channel': chanid12}, + {'amount_msat': 1000, + 'id': l3.info['id'], + 'delay': 10, + 'channel': chanid23}] + + l1.rpc.call("sendpay", payload={'route': route, + 'payment_hash': inv['payment_hash'], + 'payment_secret': inv['payment_secret'], + 'dev_legacy_hop': True}) + l1.rpc.waitsendpay(inv['payment_hash']) diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index a70074644622..f09f42a0418a 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -932,6 +932,11 @@ u8 *serialize_onionpacket( bool sphinx_add_hop_has_length(struct sphinx_path *path UNNEEDED, const struct pubkey *pubkey UNNEEDED, const u8 *payload TAKES UNNEEDED) { fprintf(stderr, "sphinx_add_hop_has_length called!\n"); abort(); } +/* Generated stub for sphinx_add_v0_hop */ +void sphinx_add_v0_hop(struct sphinx_path *path UNNEEDED, const struct pubkey *pubkey UNNEEDED, + const struct short_channel_id *scid UNNEEDED, + struct amount_msat forward UNNEEDED, u32 outgoing_cltv UNNEEDED) +{ fprintf(stderr, "sphinx_add_v0_hop called!\n"); abort(); } /* Generated stub for sphinx_path_new */ struct sphinx_path *sphinx_path_new(const tal_t *ctx UNNEEDED, const u8 *associated_data UNNEEDED)