diff --git a/plugins/keysend.c b/plugins/keysend.c index 7dff550b2..22ae85e3b 100644 --- a/plugins/keysend.c +++ b/plugins/keysend.c @@ -179,6 +179,7 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf, p->json_toks = params; p->destination = tal_steal(p, destination); p->payment_secret = NULL; + p->payment_metadata = NULL; p->amount = *msat; p->routes = tal_steal(p, hints); // 22 is the Rust-Lightning default and the highest minimum we know of. diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index f3f9d496d..1c8779f41 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -1581,7 +1581,7 @@ static struct command_result *payment_createonion_success(struct command *cmd, /* Temporary serialization method for the tlv_payload.data until we rework the * API that is generated from the specs to use the setter/getter interface. */ static void tlvstream_set_tlv_payload_data(struct tlv_field **stream, - struct secret *payment_secret, + const struct secret *payment_secret, u64 total_msat) { u8 *ser = tal_arr(NULL, u8, 0); @@ -1596,7 +1596,8 @@ static void payment_add_hop_onion_payload(struct payment *p, struct route_hop *node, struct route_hop *next, bool final, - struct secret *payment_secret) + struct secret *payment_secret, + const u8 *payment_metadata) { struct createonion_request *cr = p->createonion_request; u32 cltv = p->start_block + next->delay + 1; @@ -1627,6 +1628,11 @@ static void payment_add_hop_onion_payload(struct payment *p, fields, payment_secret, root->amount.millisatoshis); /* Raw: TLV payload generation*/ } + if (payment_metadata != NULL) { + assert(final); + tlvstream_set_raw(fields, TLV_TLV_PAYLOAD_PAYMENT_METADATA, + payment_metadata, tal_bytelen(payment_metadata)); + } } static void payment_compute_onion_payloads(struct payment *p) @@ -1665,7 +1671,7 @@ static void payment_compute_onion_payloads(struct payment *p) * i+1 */ payment_add_hop_onion_payload(p, &cr->hops[i], &p->route[i], &p->route[i + 1], false, - NULL); + NULL, NULL); tal_append_fmt(&routetxt, "%s -> ", type_to_string(tmpctx, struct short_channel_id, &p->route[i].scid)); @@ -1675,7 +1681,7 @@ static void payment_compute_onion_payloads(struct payment *p) payment_add_hop_onion_payload( p, &cr->hops[hopcount - 1], &p->route[hopcount - 1], &p->route[hopcount - 1], true, - root->payment_secret); + root->payment_secret, root->payment_metadata); tal_append_fmt(&routetxt, "%s", type_to_string(tmpctx, struct short_channel_id, &p->route[hopcount - 1].scid)); diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index e1f14da44..86aea238c 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -180,6 +180,9 @@ struct payment { /* Payment secret, from the invoice if any. */ struct secret *payment_secret; + /* Payment metadata, from the invoice if any. */ + u8 *payment_metadata; + u64 groupid; u32 partid; u32 next_partid; diff --git a/plugins/pay.c b/plugins/pay.c index 7d08e1731..9dfedfc25 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -108,6 +108,9 @@ struct pay_command { /* Payment secret, if specified by invoice. */ const char *payment_secret; + /* Payment metadata, if specified by invoice. */ + const char *payment_metadata; + /* Description, if any. */ const char *label; @@ -849,6 +852,8 @@ static struct command_result *getroute_done(struct command *cmd, json_add_string(req->js, "label", pc->label); if (pc->payment_secret) json_add_string(req->js, "payment_secret", pc->payment_secret); + if (pc->payment_metadata) + json_add_string(req->js, "payment_metadata", pc->payment_metadata); return send_outreq(cmd->plugin, req); } @@ -1368,6 +1373,11 @@ static struct command_result *json_pay(struct command *cmd, sizeof(*b11->payment_secret)); else pc->payment_secret = NULL; + if (b11->metadata) + pc->payment_metadata = tal_hex(pc, b11->metadata); + else + pc->payment_metadata = NULL; + /* We try first without using routehint */ pc->current_routehint = NULL; pc->routehints = filter_routehints(pc, b11->routes); @@ -2380,6 +2390,10 @@ static struct command_result *json_paymod(struct command *cmd, p->payment_hash = tal_dup(p, struct sha256, &b11->payment_hash); p->payment_secret = tal_dup_or_null(p, struct secret, b11->payment_secret); + if (b11->metadata) + p->payment_metadata = tal_dup_talarr(p, u8, b11->metadata); + else + p->payment_metadata = NULL; /* FIXME: libplugin-pay plays with this array, and there are many FIXMEs * about it. But it looks like a leak, so we suppress it here. */ p->routes = notleak_with_children(tal_steal(p, b11->routes)); @@ -2439,6 +2453,7 @@ static struct command_result *json_paymod(struct command *cmd, BUILD_ASSERT(sizeof(*p->payment_secret) == sizeof(merkle)); } + p->payment_metadata = NULL; p->routes = NULL; /* FIXME: paths! */ if (b12->cltv) diff --git a/tests/test_pay.py b/tests/test_pay.py index f8682643c..1778d6b83 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5238,7 +5238,24 @@ def test_pay_manual_exclude(node_factory, bitcoind): l2.rpc.pay(inv, exclude=[scid23]) +@unittest.skipIf(TEST_NETWORK != 'regtest', "Invoice is network specific") def test_pay_bolt11_metadata(node_factory, bitcoind): - l1 = node_factory.get_node() + l1, l2 = node_factory.line_graph(2) - l1.rpc.decode('lnbcrt10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqcqpjsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgqrk6hdutpaetmm3afjn0vfczgeyv0cy739rr939kwd4h5j3khxcskhgf59eaqy8wyq82tsnaqc5y32ed4jg34jw7rmeva9u6kfhymawgptmy5f6') + # BOLT #11: + # > ### Please send 0.01 BTC with payment metadata 0x01fafaf0 + # > lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc + + b11 = l1.rpc.decode('lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc') + assert b11['payment_metadata'] == '01fafaf0' + + # I previously hacked lightningd to add "this is metadata" to metadata. + # After CI started failing, I *also* hacked it to set expiry to BIGNUM. + inv = "lnbcrt1230n1p3yzgcxsp5q8g040f9rl9mu2unkjuj0vn262s6nyrhz5hythk3ueu2lfzahmzspp5ve584t0cv27hwmy0cx9ca8uwyqyfw9y9dm3r8vus9fv36r2l9yjsdq8v3jhxccmq6w35xjueqd9ejqmt9w3skgct5vyxqxra2q2qcqp99q2sqqqqqysgqfw6efxpzk5x5vfj8se46yg667x5cvhyttnmuqyk0q7rmhx3gs249qhtdggnek8c5adm2pztkjddlwyn2art2zg9xap2ckczzl3fzz4qqsej6mf" + # Make l2 "know" about this invoice. + l2.rpc.invoice(msatoshi='123000', label='label1', description='desc', preimage='00' * 32) + + with pytest.raises(RpcError, match=r'WIRE_INVALID_ONION_PAYLOAD'): + l1.rpc.pay(inv) + + l2.daemon.wait_for_log("Unexpected payment_metadata {}".format(b'this is metadata'.hex()))