Core Lightning implementation of BOLT #11 invoices - part 2

LIVE #18December 07, 2023

In this live we try to understand how Core Lightning computes the extra routing information that it puts into BOLT #11 invoice. As we examine some of the functions involved in this process, we also see how we can log data in Core Lightning log file with the functions log_debug, json_tok_full and type_to_string.

Transcript with corrections and improvements

Hi let's go! Thank you Eduardo for coming, I hope we are going to do a great session today.

This is the second part of a series of live and videos on LNROOM and CLN on BOL2 about BOLT #11 invoices.

Last time we saw how to implement JSON RPC commands in lightningd.

This is interesting for at least 2 reasons:

  1. this gives us a tool for understanding many parts of Core Lightning,

  2. this gives us a tool to add our own commands to Core Lightning.

We did that because we want to understand how the invoice command is implemented.

And today we're going to try to understand how Core Lightning computes the extra routing information that it puts into BOLT #11 invoice.

As we examine some of the functions involved in this process, we will see how we can log data in Core Lightning log file with the functions log_debug, json_tok_full and type_to_string.

Extra routing information tagged field defined in BOLT #11

From BOLT #11: Invoice Protocol for Lightning Payments:

Each Tagged Field is of the form:

  1. type (5 bits)

  2. data_length (10 bits, big-endian)

  3. data (data_length x 5 bits)

Note that the maximum length of a Tagged Field's data is constricted by the maximum value of data_length. This is 1023 x 5 bits, or 639 bytes.

Currently defined tagged fields are:

  • ...

  • r (3): data_length variable. One or more entries containing extra routing information for a private route; there may be more than one r field

    • pubkey (264 bits)

    • short_channel_id (64 bits)

    • fee_base_msat (32 bits, big-endian)

    • fee_proportional_millionths (32 bits, big-endian)

    • cltv_expiry_delta (16 bits, big-endian)

  • ...

Why I wanted to talk about extra routing information before something else?

Because when we try to understand the implementation of the invoice command and we try to get to the point where we build the JSON stream (the output of the invoice command) we need to understand why we call the command listincoming defined in the plugin topology. And this is because we take the information returned by listincoming to compute the extra routing information that we then encode with bech32 encoding following BOLT #11 specification.

invoice command

Let's look at the invoice command. It takes 3 mandatory arguments amount_msat, label and description and some optional ones:

invoice  amount_msat label description
         [expiry] [fallbacks] [preimage]
         [exposeprivatechannels] [cltv] [deschashonly]

If we run that command at the command line where l1-cli is an alias for lightning-cli --lightning-dir=/tmp/l1-regtest we get the following BOLT #11 invoice:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli invoice 10000 label-1 description-1
{
   "payment_hash": "de57fd9e5d2190c17d3e8d95f980ebdb5c6da7e67128f505033df1e6bfc5643a",
   "expires_at": 1701338404,
   "bolt11": "lnbcrt100n1pj47g4...r8rsyy",
   "payment_secret": "17ace961ae413115c8944c874f78f1834ab54b6234d009e7744a262d9c9acd5e",
   "created_index": 1,
   "warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}

Adding a command to lightningd

Example of getinfo defined in lightning:lightningd/peer_control.c:

static struct command_result *json_getinfo(...) {...}
static const struct json_command getinfo_command = {...};
AUTODATA(json_command, &getinfo_command);

Helpers:

my-invoice

/* clnlive #17 */

static struct command_result *json_my_invoice(struct command *cmd,
                                              const char *buffer,
                                              const jsmntok_t *obj UNNEEDED,
                                              const jsmntok_t *params)
{
        struct amount_msat *msatoshi_val;
        const char *desc_val;
        struct json_stream *response;

        if (!param(cmd, buffer, params,
                   p_req("amount_msat", param_msat, &msatoshi_val),
                   p_opt("description", param_escaped_string, &desc_val),
                   NULL))
                return command_param_failed();

        response = json_stream_success(cmd);
        json_add_amount_msat(response, "amount_msat", *msatoshi_val);
        if (desc_val)
                json_add_string(response, "description", desc_val);
        return command_success(cmd, response);
}

static const struct json_command my_invoice_command = {
        "my-invoice",
        "clnlive",
        json_my_invoice,
        "my-invoice clnlive #17"};
AUTODATA(json_command, &my_invoice_command);

Start 2 Lightning nodes running on regtest

Let's start two Lightning nodes running on the Bitcoin regtest chain by sourcing the script contrib/startup_regtest.sh and running the command start_ln:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/clnlive/lightning/cli/lightning-cli
lightningd is /home/tony/clnlive/lightning/lightningd/lightningd
Useful commands:
  start_ln 3: start three nodes, l1, l2, l3
  connect 1 2: connect l1 and l2
  fund_nodes: connect all nodes with channels, in a row
  stop_ln: shutdown
  destroy_ln: remove ln directories
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 911045
[2] 911084
WARNING: eatmydata not found: install it for faster testing
Commands:
        l1-cli, l1-log,
        l2-cli, l2-log,
        bt-cli, stop_ln, fund_nodes

Note that I've already compile CLN so the script set the following:

lightning-cli is /home/tony/clnlive/lightning/cli/lightning-cli
lightningd is /home/tony/clnlive/lightning/lightningd/lightningd

Decode invoices, listincoming and routhint logs

Now we create an invoice with the node l2:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-1 desc-1
{
   "payment_hash": "aa1a254676ec9fe1ec1be8fb85e6e1f03cb5657fc73e4145ce501ab900fdc1f6",
   "expires_at": 1702568288,
   "bolt11": "lnbcrt100n1pjhrehqsp5xngwwzln3aqh7c5t33znrqyfe8t88cjr2ztz3dxt3l7zh2kwftlspp54gdz23nkaj07rmqmaractehp7q7t2etlculyz3ww2qdtjq8ac8mqdq2v3jhxcedxyxqyjw5qcqp2fp4ppmd34yjezsvdny34s9zd62n4fgr43sxj8yu7lz0vkkcqem8tlpcs9qx3qysgq0k8gptpm2yruu04c7exwswshewm9gn3hhp2vdqg5mzyma0geh3en7a9f3m6pn9l4tmg92hsct4luxs7j26aamas5hv4hkkdsnmvla2gp0ry24v",
   "payment_secret": "34d0e70bf38f417f628b8c45318089c9d673e243509628b4cb8ffc2baace4aff",
   "created_index": 1,
   "warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}

Running that command triggered at some point the call to the functions add_routehints and routehint_candidates responsible for the following lines in the l2's log file /tmp/l2-regtest/log:

2023-12-07T15:38:08.305Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#911468/cln:listincoming#82","result":{"incoming":[]}}
2023-12-07T15:38:08.305Z DEBUG   lightningd: No publics: using private channels
2023-12-07T15:38:08.305Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 0msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat

The first one shows the JSON RPC response to a listincoming request (defined in the topology plugin) that we can also get (only the result part) by running the listincoming command at the command line:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": []
}

As we have no open incoming channels, we get an empty array.

The elements in that array (when we have incoming channels) are used to compute the extra routing information that goes into the BOLT #11 invoice.

Now let's funds a channel from the node l1 to the node l2 using fund_nodes command provided by contrib/startup_regtest.sh script we sourced above:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ fund_nodes
Mining into address bcrt1q7576cah6ku73e7cs903649e6m8pcp8u7p80p6x... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.

We observe that incoming channel to the l2 node is listed by listincoming command:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": [
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "103x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 990380032,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 0,
         "public": true,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      }
   ]
}

Let's create another invoice

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-2 desc-2
{
   "payment_hash": "15484e6e592378048ff274288ee400c57bbd3391fc2d4b343b43a3929b9fad32",
   "expires_at": 1702568497,
   "bolt11": "lnbcrt100n1pjhrea3sp5n8pt99jg00wd5d40lfxmds0guxfpr4gakzs3yx4e9h73n4xxwgxspp5z4yyumjeyduqfrljws5gaeqqc4am6vu3lsk5kdpmgw3e9xul45eqdq2v3jhxcedxgxqyjw5qcqp2fp4pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9s9qx3qysgqcqluyu3mj3ul8lx65mqtft0yueexstz6t57pazgcd5tm45py290ptkapuyq3zt3ml6xcfm8pp4j0lz3wpch06vnhqgkpnmq2c6mnl0cq9kahnf",
   "payment_secret": "99c2b296487bdcda36affa4db6c1e8e19211d51db0a1121ab92dfd19d4c6720d",
   "created_index": 2,
   "warning_deadends": "Insufficient incoming capacity, once dead-end peers were excluded"
}

and decode it like this:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli decode lnbcrt100n1pjhrea3sp5n8pt99jg00wd5d40lfxmds0guxfpr4gakzs3yx4e9h73n4xxwgxspp5z4yyumjeyduqfrljws5gaeqqc4am6vu3lsk5kdpmgw3e9xul45eqdq2v3jhxcedxgxqyjw5qcqp2fp4pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9s9qx3qysgqcqluyu3mj3ul8lx65mqtft0yueexstz6t57pazgcd5tm45py290ptkapuyq3zt3ml6xcfm8pp4j0lz3wpch06vnhqgkpnmq2c6mnl0cq9kahnf
{
   "type": "bolt11 invoice",
   "currency": "bcrt",
   "created_at": 1701963697,
   "expiry": 604800,
   "payee": "033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b",
   "amount_msat": 10000,
   "description": "desc-2",
   "min_final_cltv_expiry": 10,
   "payment_secret": "99c2b296487bdcda36affa4db6c1e8e19211d51db0a1121ab92dfd19d4c6720d",
   "features": "22024100",
   "fallbacks": [
      {
         "type": "P2TR",
         "addr": "bcrt1pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9sekv0ln",
         "hex": "5120e6d2475675441ce102e16aa016aeb3fe7e147b39ab1d897a3c70f620f12b7e4b"
      }
   ],
   "payment_hash": "15484e6e592378048ff274288ee400c57bbd3391fc2d4b343b43a3929b9fad32",
   "signature": "3045022100c03fc2723b9479f3fcdaa6c0b4ade4e672682c5a5d3c1e89186d17bad024515e022015dba1e101112e3bfe8d84ece10d64ff8a2e0e2efd3277022c19ec0ac6b73fbf",
   "valid": true
}

Although l2 has an incoming channel, we don't see any extra routing information in that invoice.

Let's create another channel from l1 to l2 like this:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli getinfo | jq -r .id
033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli fundchannel 033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b 100000
{
   "tx": "02000000000101776490dcf34253655ad24df14fc8d568ae737bd6799ecae85c31e74281013dc60100000000fdffffff02a086010000000000220020b5b1860da4f9ca637bc5c7fdc1151df6ac5989f52ac49183e44f6f9ed9cab8dcd416e50500000000225120dcc7bbef8128db5cdb25f1ef723b10241f5563cdf2d62de7ab0a343a0b09d0b2014063d631924d16a576998912cccb8587500e0f1798581dc37cfc397c8aab0ab5f04f9a3d3e656a5fbef32ac89fbd940f1468ea01831593b6760e9902be17c0b5866c000000",
   "txid": "380b742f9ef1f847b0ac878e250b28ff934a80027146699da53487970498ab20",
   "channel_id": "88f88b447b98d4b6548b51d42b663fa268b90372275481ca6f27bf70343aa8be",
   "outnum": 0
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
bcrt1qjp4u52rlk09puuzqc2qav5clchjmfe6ef43n7d
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1qjp4u52rlk09puuzqc2qav5clchjmfe6ef43n7d
[
  "5741128e4f6e69b348645a0cb6646aafa706d732ee498a3bb392af7802929d09"
]

We now have two incoming channels listed by listincoming command:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": [
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "103x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 990380032,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 99024896,
         "public": true,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      },
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "109x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 99024896,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 990380032,
         "public": false,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      }
   ]
}

Finally, we create another invoice

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-3 desc-3
{
   "payment_hash": "71484275391b3b80c0efb3ed8504dec53a1efefef12f7176b80bdc17ef77c249",
   "expires_at": 1702568646,
   "bolt11": "lnbcrt100n1pjhr6zxsp5fy3cg4tm5cc3c7k86yz55z2k5ag5gj40prl5mdsnymwrqt800znspp5w9yyyafervacps80k0kc2px7c5apalh77yhhza4cp0wp0mmhcfysdq2v3jhxcedxvxqyjw5qcqp2fp4peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qptsrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqfvsju83a2kqnnvshzljtgpn2w8k7gfhhtqnuzq0hnequga8lpzu8tchnwjjqecqf394kz5hmxu8jrp3u6elj0t84qxmn8mch8ta2txsp88aas4",
   "payment_secret": "492384557ba6311c7ac7d1054a0956a751444aaf08ff4db61326dc302cef78a7",
   "created_index": 3
}

which include extra routing information in the routes field of its decoded version returned by the decode command:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli decode lnbcrt100n1pjhr6zxsp5fy3cg4tm5cc3c7k86yz55z2k5ag5gj40prl5mdsnymwrqt800znspp5w9yyyafervacps80k0kc2px7c5apalh77yhhza4cp0wp0mmhcfysdq2v3jhxcedxvxqyjw5qcqp2fp4peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qptsrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqfvsju83a2kqnnvshzljtgpn2w8k7gfhhtqnuzq0hnequga8lpzu8tchnwjjqecqf394kz5hmxu8jrp3u6elj0t84qxmn8mch8ta2txsp88aas4
{
   "type": "bolt11 invoice",
   "currency": "bcrt",
   "created_at": 1701963846,
   "expiry": 604800,
   "payee": "033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b",
   "amount_msat": 10000,
   "description": "desc-3",
   "min_final_cltv_expiry": 10,
   "payment_secret": "492384557ba6311c7ac7d1054a0956a751444aaf08ff4db61326dc302cef78a7",
   "features": "22024100",
   "fallbacks": [
      {
         "type": "P2TR",
         "addr": "bcrt1peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qpts7ql0tc",
         "hex": "5120cf6f618cf861f6dae2bdf102fd18ecee77c32e19a594377140dcfb1265350057"
      }
   ],
   "routes": [
      [
         {
            "pubkey": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
            "short_channel_id": "109x1x0",
            "fee_base_msat": 1,
            "fee_proportional_millionths": 10,
            "cltv_expiry_delta": 6
         }
      ]
   ],
   "payment_hash": "71484275391b3b80c0efb3ed8504dec53a1efefef12f7176b80bdc17ef77c249",
   "signature": "304402204b212e1e3d558139b21717e4b4066a71ede426f75827c101f79e41c474ff08b8022075e2f374a40ce009896b6152fb370f21863cd67f27acf501b733ef173afaa59a",
   "valid": true
}

We can check again in the log the lines added because we called the invoice command:

...
2023-12-07T15:41:37.329Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#912577/cln:listincoming#280","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":0,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T15:41:37.329Z DEBUG   lightningd: 103x1x0: deadend
2023-12-07T15:41:37.329Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 0msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 989460000msat
...
2023-12-07T15:44:06.479Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#913058/cln:listincoming#349","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T15:44:06.479Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T15:44:06.479Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T15:44:06.479Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087920000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...

Printing lines in the log file

Compiling CLN with rust plugin disabled

In the lightning repository checked out at v23.11 (the latest release), we run the configure file and we build it (-j4 because I have 4 CPUs on my machine running Ubuntu 22.04.2 LTS):

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ ./configure --disable-rust
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ make -j4
...

Note that I have all the build dependencies already installed on my machine (See Installing from source).

In the directory lightningd the binary lightningd has been built among other like channeld.

Print listincoming JSON RPC response in the log with log_debug

We modify routehint_candidates function by copying the statement that prints listincoming JSON RPC response in the log and replace log_debug by log_info like this:

struct routehint_candidate *
routehint_candidates(const tal_t *ctx,
                     struct lightningd *ld,
                     const char *buf,
                     const jsmntok_t *toks,
                     /* ... */
                     )
{
        ...
        log_info(ld->log, "routehint: %.*s",
                  json_tok_full_len(toks),
                  json_tok_full(buf, toks));
        log_debug(ld->log, "routehint: %.*s",
                  json_tok_full_len(toks),
                  json_tok_full(buf, toks));
        ...
        return candidates;
}

Then we compile lightningd and restart l2 node:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ make -j4
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-cli
alias l2-cli='/home/tony/clnlive/lightning/cli/lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest --daemon'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-start

Now we create an invoice

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-4 desc-4
{
   "payment_hash": "31c8aec1387d0851105dd5dff7f133f590e39d18149389cf30bcb66b555784f1",
   "expires_at": 1702569777,
   "bolt11": "lnbcrt100n1pjhrm93sp53jnu6kmxzg9aj0e6v9ez5vwja6kmd75tht4k3zqj5zqnlh6rrjnqpp5x8y2asfc05y9zyza6h0l0ufn7kgw88gczjfcnneshjmxk42hsncsdq2v3jhxcedxsxqyjw5qcqp2fp4pzmwv5rkka8e0gy0j58m4r2drv95u9eljave07adazp3aacq66jksrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgq063gyf4jcs3f80qsv43a8jz9zlz06cg06rmv4xmauhs8f93vxd0q9xtg8unm85puslhaq3pp5rslyydg57mskmxya6futzveqtmpapspwzatjv",
   "payment_secret": "8ca7cd5b66120bd93f3a61722a31d2eeadb6fa8bbaeb688812a0813fdf431ca6",
   "created_index": 4
}

which produces the following lines in the log file /tmp/l2-regtest/log:

2023-12-07T16:02:29.861Z INFO    lightningd: v23.11-modded
2023-12-07T16:02:29.930Z DEBUG   lightningd: Opened log file /tmp/l2-regtest/log
...
2023-12-07T16:02:57.072Z INFO    lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#929515/cln:listincoming#70","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:02:57.072Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#929515/cln:listincoming#70","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:02:57.072Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T16:02:57.072Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T16:02:57.072Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087898000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...

Print short channel id in the log with log_debug and type_to_string

We modify routehint_candidates function such that we print the short channel id of each incoming channel present in listincoming JSON RPC response:

struct routehint_candidate *
routehint_candidates(const tal_t *ctx,
                     struct lightningd *ld,
                     const char *buf,
                     const jsmntok_t *toks,
                     /* ... */
                     )
{
        ...
        t = json_get_member(buf, toks, "result");
        ...
        arr = json_get_member(buf, t, "incoming");
        ...
        json_for_each_arr(i, t, arr) {
                ...
                log_debug(ld->log, "routehints: short_channel_id (%s)",
                          type_to_string(tmpctx,
                                         struct short_channel_id,
                                         &r->short_channel_id));
        ...
        }
        ...
        return candidates;
}

Then we compile lightningd and restart l2 node:

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ make -j4
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-start

Now we create an invoice

◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-5 desc-5
{
   "payment_hash": "72c8ccb46a155ca4620d4801201fde6fb650d135288f28e337d2c0dd5b84f1d0",
   "expires_at": 1702570441,
   "bolt11": "lnbcrt100n1pjhrm6fsp5j8z64dcf8m9nscrgk72gny7edeqdlzp253aggggnlcxe78d4k7gqpp5wtyvedr2z4w2gcsdfqqjq877d7m9p5f49z8j3ceh6tqd6kuy78gqdq2v3jhxcedx5xqyjw5qcqp2fp4pp6xfg2eqf04vjczk2q3r2cldne8jd03yhz7837fd6zddptmtd02qrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqt59fce7tkgskpuhlp5de5z7rz834rhnc78x6jwwkyghwcg7f7c5pvugtrt444lc7dnd28pkgnx5hsa5c229pdhh5e9dt9m0zrqepavcp674t0l",
   "payment_secret": "91c5aab7093ecb386068b7948993d96e40df882aa47a842113fe0d9f1db5b790",
   "created_index": 5
}

which produces the following lines in the log file /tmp/l2-regtest/log:

2023-12-07T16:13:53.412Z INFO    lightningd: v23.11-modded
2023-12-07T16:13:53.481Z DEBUG   lightningd: Opened log file /tmp/l2-regtest/log
...
2023-12-07T16:14:01.714Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#937596/cln:listincoming#67","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:14:01.714Z DEBUG   lightningd: routehints: short_channel_id (103x1x0)
2023-12-07T16:14:01.715Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T16:14:01.715Z DEBUG   lightningd: routehints: short_channel_id (109x1x0)
2023-12-07T16:14:01.715Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T16:14:01.715Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087898000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat

Note that during the live session I said that type_to_string could be use for the production of the JSON streams of RPC commands, but this is incorrect. This is not the way to do it. Instead, we must use the functions defined in lightning:common/json_stream.c file. For instance, if we want to add a short channel id in a JSON stream we must use json_add_short_channel_id function.

Terminal session

We ran the following commands in this order:

$ cd ..
$ source contrib/startup_regtest.sh
$ start_ln
$ l2-cli invoice 10000 label-1 desc-1
$ l2-cli listincoming
$ fund_nodes
$ l2-cli listincoming
$ l2-cli invoice 10000 label-2 desc-2
$ l2-cli decode lnbcrt100n1pjhrea3sp5n8pt99jg00wd5d40lfxmds0guxfpr4gakzs3yx4e9h73n4xxwgxspp5z4yyumjeyduqfrljws5gaeqqc4am6vu3lsk5kdpmgw3e9xul45eqdq2v3jhxcedxgxqyjw5qcqp2fp4pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9s9qx3qysgqcqluyu3mj3ul8lx65mqtft0yueexstz6t57pazgcd5tm45py290ptkapuyq3zt3ml6xcfm8pp4j0lz3wpch06vnhqgkpnmq2c6mnl0cq9kahnf
$ l2-cli getinfo | jq -r .id
$ l1-cli fundchannel 033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b 100000
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1qjp4u52rlk09puuzqc2qav5clchjmfe6ef43n7d
$ l2-cli listincoming
$ l2-cli invoice 10000 label-3 desc-3
$ l2-cli decode lnbcrt100n1pjhr6zxsp5fy3cg4tm5cc3c7k86yz55z2k5ag5gj40prl5mdsnymwrqt800znspp5w9yyyafervacps80k0kc2px7c5apalh77yhhza4cp0wp0mmhcfysdq2v3jhxcedxvxqyjw5qcqp2fp4peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qptsrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqfvsju83a2kqnnvshzljtgpn2w8k7gfhhtqnuzq0hnequga8lpzu8tchnwjjqecqf394kz5hmxu8jrp3u6elj0t84qxmn8mch8ta2txsp88aas4
$ alias l2-cli
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest'
$ l2-cli stop
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest --daemon'
$ l2-start
$ l2-cli invoice 10000 label-4 desc-4
$ l2-cli stop
$ l2-start
$ l2-cli invoice 10000 label-5 desc-5

And below you can read the terminal session (command lines and outputs):

◉ tony@tony:~/clnlive/lightning/lightningd:[git»(HEAD detached at v23.11)]
$ cd ..
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/clnlive/lightning/cli/lightning-cli
lightningd is /home/tony/clnlive/lightning/lightningd/lightningd
Useful commands:
  start_ln 3: start three nodes, l1, l2, l3
  connect 1 2: connect l1 and l2
  fund_nodes: connect all nodes with channels, in a row
  stop_ln: shutdown
  destroy_ln: remove ln directories
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 911045
[2] 911084
WARNING: eatmydata not found: install it for faster testing
Commands:
        l1-cli, l1-log,
        l2-cli, l2-log,
        bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-1 desc-1
{
   "payment_hash": "aa1a254676ec9fe1ec1be8fb85e6e1f03cb5657fc73e4145ce501ab900fdc1f6",
   "expires_at": 1702568288,
   "bolt11": "lnbcrt100n1pjhrehqsp5xngwwzln3aqh7c5t33znrqyfe8t88cjr2ztz3dxt3l7zh2kwftlspp54gdz23nkaj07rmqmaractehp7q7t2etlculyz3ww2qdtjq8ac8mqdq2v3jhxcedxyxqyjw5qcqp2fp4ppmd34yjezsvdny34s9zd62n4fgr43sxj8yu7lz0vkkcqem8tlpcs9qx3qysgq0k8gptpm2yruu04c7exwswshewm9gn3hhp2vdqg5mzyma0geh3en7a9f3m6pn9l4tmg92hsct4luxs7j26aamas5hv4hkkdsnmvla2gp0ry24v",
   "payment_secret": "34d0e70bf38f417f628b8c45318089c9d673e243509628b4cb8ffc2baace4aff",
   "created_index": 1,
   "warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": []
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ fund_nodes
Mining into address bcrt1q7576cah6ku73e7cs903649e6m8pcp8u7p80p6x... done.
bitcoind balance: 50.00000000
Waiting for lightning node funds... found.
Funding channel from node 1 to node 2. Waiting for confirmation... done.
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": [
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "103x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 990380032,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 0,
         "public": true,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      }
   ]
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-2 desc-2
{
   "payment_hash": "15484e6e592378048ff274288ee400c57bbd3391fc2d4b343b43a3929b9fad32",
   "expires_at": 1702568497,
   "bolt11": "lnbcrt100n1pjhrea3sp5n8pt99jg00wd5d40lfxmds0guxfpr4gakzs3yx4e9h73n4xxwgxspp5z4yyumjeyduqfrljws5gaeqqc4am6vu3lsk5kdpmgw3e9xul45eqdq2v3jhxcedxgxqyjw5qcqp2fp4pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9s9qx3qysgqcqluyu3mj3ul8lx65mqtft0yueexstz6t57pazgcd5tm45py290ptkapuyq3zt3ml6xcfm8pp4j0lz3wpch06vnhqgkpnmq2c6mnl0cq9kahnf",
   "payment_secret": "99c2b296487bdcda36affa4db6c1e8e19211d51db0a1121ab92dfd19d4c6720d",
   "created_index": 2,
   "warning_deadends": "Insufficient incoming capacity, once dead-end peers were excluded"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli decode lnbcrt100n1pjhrea3sp5n8pt99jg00wd5d40lfxmds0guxfpr4gakzs3yx4e9h73n4xxwgxspp5z4yyumjeyduqfrljws5gaeqqc4am6vu3lsk5kdpmgw3e9xul45eqdq2v3jhxcedxgxqyjw5qcqp2fp4pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9s9qx3qysgqcqluyu3mj3ul8lx65mqtft0yueexstz6t57pazgcd5tm45py290ptkapuyq3zt3ml6xcfm8pp4j0lz3wpch06vnhqgkpnmq2c6mnl0cq9kahnf
{
   "type": "bolt11 invoice",
   "currency": "bcrt",
   "created_at": 1701963697,
   "expiry": 604800,
   "payee": "033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b",
   "amount_msat": 10000,
   "description": "desc-2",
   "min_final_cltv_expiry": 10,
   "payment_secret": "99c2b296487bdcda36affa4db6c1e8e19211d51db0a1121ab92dfd19d4c6720d",
   "features": "22024100",
   "fallbacks": [
      {
         "type": "P2TR",
         "addr": "bcrt1pumfyw4n4gswwzqhpd2spdt4nlelpg7ee4vwcj73uwrmzpuft0e9sekv0ln",
         "hex": "5120e6d2475675441ce102e16aa016aeb3fe7e147b39ab1d897a3c70f620f12b7e4b"
      }
   ],
   "payment_hash": "15484e6e592378048ff274288ee400c57bbd3391fc2d4b343b43a3929b9fad32",
   "signature": "3045022100c03fc2723b9479f3fcdaa6c0b4ade4e672682c5a5d3c1e89186d17bad024515e022015dba1e101112e3bfe8d84ece10d64ff8a2e0e2efd3277022c19ec0ac6b73fbf",
   "valid": true
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli getinfo | jq -r .id
033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli fundchannel 033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b 100000
{
   "tx": "02000000000101776490dcf34253655ad24df14fc8d568ae737bd6799ecae85c31e74281013dc60100000000fdffffff02a086010000000000220020b5b1860da4f9ca637bc5c7fdc1151df6ac5989f52ac49183e44f6f9ed9cab8dcd416e50500000000225120dcc7bbef8128db5cdb25f1ef723b10241f5563cdf2d62de7ab0a343a0b09d0b2014063d631924d16a576998912cccb8587500e0f1798581dc37cfc397c8aab0ab5f04f9a3d3e656a5fbef32ac89fbd940f1468ea01831593b6760e9902be17c0b5866c000000",
   "txid": "380b742f9ef1f847b0ac878e250b28ff934a80027146699da53487970498ab20",
   "channel_id": "88f88b447b98d4b6548b51d42b663fa268b90372275481ca6f27bf70343aa8be",
   "outnum": 0
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ bitcoin-cli -regtest -rpcwallet=default getnewaddress
bcrt1qjp4u52rlk09puuzqc2qav5clchjmfe6ef43n7d
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ bitcoin-cli -regtest generatetoaddress 1 bcrt1qjp4u52rlk09puuzqc2qav5clchjmfe6ef43n7d
[
  "5741128e4f6e69b348645a0cb6646aafa706d732ee498a3bb392af7802929d09"
]
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli listincoming
{
   "incoming": [
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "103x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 990380032,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 99024896,
         "public": true,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      },
      {
         "id": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
         "short_channel_id": "109x1x0",
         "fee_base_msat": 1,
         "htlc_min_msat": 0,
         "htlc_max_msat": 99024896,
         "fee_proportional_millionths": 10,
         "cltv_expiry_delta": 6,
         "incoming_capacity_msat": 990380032,
         "public": false,
         "peer_features": "080000000000000000000000000088a0882a0a69a2"
      }
   ]
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-3 desc-3
{
   "payment_hash": "71484275391b3b80c0efb3ed8504dec53a1efefef12f7176b80bdc17ef77c249",
   "expires_at": 1702568646,
   "bolt11": "lnbcrt100n1pjhr6zxsp5fy3cg4tm5cc3c7k86yz55z2k5ag5gj40prl5mdsnymwrqt800znspp5w9yyyafervacps80k0kc2px7c5apalh77yhhza4cp0wp0mmhcfysdq2v3jhxcedxvxqyjw5qcqp2fp4peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qptsrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqfvsju83a2kqnnvshzljtgpn2w8k7gfhhtqnuzq0hnequga8lpzu8tchnwjjqecqf394kz5hmxu8jrp3u6elj0t84qxmn8mch8ta2txsp88aas4",
   "payment_secret": "492384557ba6311c7ac7d1054a0956a751444aaf08ff4db61326dc302cef78a7",
   "created_index": 3
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli decode lnbcrt100n1pjhr6zxsp5fy3cg4tm5cc3c7k86yz55z2k5ag5gj40prl5mdsnymwrqt800znspp5w9yyyafervacps80k0kc2px7c5apalh77yhhza4cp0wp0mmhcfysdq2v3jhxcedxvxqyjw5qcqp2fp4peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qptsrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqfvsju83a2kqnnvshzljtgpn2w8k7gfhhtqnuzq0hnequga8lpzu8tchnwjjqecqf394kz5hmxu8jrp3u6elj0t84qxmn8mch8ta2txsp88aas4
{
   "type": "bolt11 invoice",
   "currency": "bcrt",
   "created_at": 1701963846,
   "expiry": 604800,
   "payee": "033dc1a46e4f606afc61a108200494baedcfe39ebf7a978967ef63666cffe3945b",
   "amount_msat": 10000,
   "description": "desc-3",
   "min_final_cltv_expiry": 10,
   "payment_secret": "492384557ba6311c7ac7d1054a0956a751444aaf08ff4db61326dc302cef78a7",
   "features": "22024100",
   "fallbacks": [
      {
         "type": "P2TR",
         "addr": "bcrt1peahkrr8cv8md4c4a7yp06x8vaemuxtse5k2rwu2qmna3yef4qpts7ql0tc",
         "hex": "5120cf6f618cf861f6dae2bdf102fd18ecee77c32e19a594377140dcfb1265350057"
      }
   ],
   "routes": [
      [
         {
            "pubkey": "027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94",
            "short_channel_id": "109x1x0",
            "fee_base_msat": 1,
            "fee_proportional_millionths": 10,
            "cltv_expiry_delta": 6
         }
      ]
   ],
   "payment_hash": "71484275391b3b80c0efb3ed8504dec53a1efefef12f7176b80bdc17ef77c249",
   "signature": "304402204b212e1e3d558139b21717e4b4066a71ede426f75827c101f79e41c474ff08b8022075e2f374a40ce009896b6152fb370f21863cd67f27acf501b733ef173afaa59a",
   "valid": true
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-cli
alias l2-cli='/home/tony/clnlive/lightning/cli/lightning-cli --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ alias l2-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l2-regtest --daemon'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-4 desc-4
{
   "payment_hash": "31c8aec1387d0851105dd5dff7f133f590e39d18149389cf30bcb66b555784f1",
   "expires_at": 1702569777,
   "bolt11": "lnbcrt100n1pjhrm93sp53jnu6kmxzg9aj0e6v9ez5vwja6kmd75tht4k3zqj5zqnlh6rrjnqpp5x8y2asfc05y9zyza6h0l0ufn7kgw88gczjfcnneshjmxk42hsncsdq2v3jhxcedxsxqyjw5qcqp2fp4pzmwv5rkka8e0gy0j58m4r2drv95u9eljave07adazp3aacq66jksrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgq063gyf4jcs3f80qsv43a8jz9zlz06cg06rmv4xmauhs8f93vxd0q9xtg8unm85puslhaq3pp5rslyydg57mskmxya6futzveqtmpapspwzatjv",
   "payment_secret": "8ca7cd5b66120bd93f3a61722a31d2eeadb6fa8bbaeb688812a0813fdf431ca6",
   "created_index": 4
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.11)]
$ l2-cli invoice 10000 label-5 desc-5
{
   "payment_hash": "72c8ccb46a155ca4620d4801201fde6fb650d135288f28e337d2c0dd5b84f1d0",
   "expires_at": 1702570441,
   "bolt11": "lnbcrt100n1pjhrm6fsp5j8z64dcf8m9nscrgk72gny7edeqdlzp253aggggnlcxe78d4k7gqpp5wtyvedr2z4w2gcsdfqqjq877d7m9p5f49z8j3ceh6tqd6kuy78gqdq2v3jhxcedx5xqyjw5qcqp2fp4pp6xfg2eqf04vjczk2q3r2cldne8jd03yhz7837fd6zddptmtd02qrzjqfuqt29a3utk3jakem4w9qa6ruh3tzfu6cfu0cfvqhqd48stl3wfgqqqd5qqqqgqqqqqqqqpqqqqqzsqqc9qx3qysgqt59fce7tkgskpuhlp5de5z7rz834rhnc78x6jwwkyghwcg7f7c5pvugtrt444lc7dnd28pkgnx5hsa5c229pdhh5e9dt9m0zrqepavcp674t0l",
   "payment_secret": "91c5aab7093ecb386068b7948993d96e40df882aa47a842113fe0d9f1db5b790",
   "created_index": 5
}

l2's log file

/tmp/l2-regtest/log

2023-12-07T15:36:07.212Z INFO    lightningd: v23.11
2023-12-07T15:36:07.309Z INFO    lightningd: Creating configuration directory /tmp/l2-regtest/regtest
2023-12-07T15:36:07.309Z DEBUG   lightningd: Opened log file /tmp/l2-regtest/log
...

2023-12-07T15:38:08.305Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#911468/cln:listincoming#82","result":{"incoming":[]}}
2023-12-07T15:38:08.305Z DEBUG   lightningd: No publics: using private channels
2023-12-07T15:38:08.305Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 0msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...
2023-12-07T15:41:37.329Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#912577/cln:listincoming#280","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":0,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T15:41:37.329Z DEBUG   lightningd: 103x1x0: deadend
2023-12-07T15:41:37.329Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 0msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 989460000msat
...
2023-12-07T15:44:06.479Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#913058/cln:listincoming#349","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T15:44:06.479Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T15:44:06.479Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T15:44:06.479Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087920000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...



2023-12-07T16:02:29.861Z INFO    lightningd: v23.11-modded
2023-12-07T16:02:29.930Z DEBUG   lightningd: Opened log file /tmp/l2-regtest/log
...
2023-12-07T16:02:57.072Z INFO    lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#929515/cln:listincoming#70","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:02:57.072Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#929515/cln:listincoming#70","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:02:57.072Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T16:02:57.072Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T16:02:57.072Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087898000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...



2023-12-07T16:13:53.412Z INFO    lightningd: v23.11-modded
2023-12-07T16:13:53.481Z DEBUG   lightningd: Opened log file /tmp/l2-regtest/log
...
2023-12-07T16:14:01.714Z DEBUG   lightningd: routehint: {"jsonrpc":"2.0","id":"cli:invoice#937596/cln:listincoming#67","result":{"incoming":[{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"103x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":990380032,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":99024896,"public":true,"peer_features":"080000000000000000000000000088a0882a0a69a2"},{"id":"027805a8bd8f1768cbb6ceeae283ba1f2f15893cd613c7e12c05c0da9e0bfc5c94","short_channel_id":"109x1x0","fee_base_msat":1,"htlc_min_msat":0,"htlc_max_msat":99024896,"fee_proportional_millionths":10,"cltv_expiry_delta":6,"incoming_capacity_msat":990380032,"public":false,"peer_features":"080000000000000000000000000088a0882a0a69a2"}]}}
2023-12-07T16:14:01.714Z DEBUG   lightningd: routehints: short_channel_id (103x1x0)
2023-12-07T16:14:01.715Z DEBUG   lightningd: 103x1x0: added to public
2023-12-07T16:14:01.715Z DEBUG   lightningd: routehints: short_channel_id (109x1x0)
2023-12-07T16:14:01.715Z DEBUG   lightningd: 109x1x0: added to public
2023-12-07T16:14:01.715Z DEBUG   lightningd: needed = 10000msat, avail_capacity = 1087898000msat, private_capacity = 0msat, offline_capacity = 0msat, deadend_capacity = 0msat
...

Core Lightning source code

json_invoice

json_invoice in lightning:lightningd/invoice.c:

static struct command_result *json_invoice(struct command *cmd,
                                           const char *buffer,
                                           const jsmntok_t *obj UNNEEDED,
                                           const jsmntok_t *params)
{
        const jsmntok_t *fallbacks;
        struct amount_msat *msatoshi_val;
        struct invoice_info *info;
        const char *desc_val;
        const u8 **fallback_scripts = NULL;
        u64 *expiry;
        struct sha256 rhash;
        struct secret payment_secret;
        struct preimage *preimage;
        u32 *cltv;
        struct jsonrpc_request *req;
        struct plugin *plugin;
        bool *hashonly;
        const size_t inv_max_label_len = 128;
        const jsmntok_t *dev_routes;

        info = tal(cmd, struct invoice_info);
        info->cmd = cmd;

        if (!param_check(cmd, buffer, params,
                         p_req("amount_msat|msatoshi", param_positive_msat_or_any, &msatoshi_val),
                         p_req("label", param_label, &info->label),
                         p_req("description", param_escaped_string, &desc_val),
                         p_opt_def("expiry", param_u64, &expiry, 3600*24*7),
                         p_opt("fallbacks", param_array, &fallbacks),
                         p_opt("preimage", param_preimage, &preimage),
                         p_opt("exposeprivatechannels", param_chanhints,
                               &info->chanhints),
                         p_opt_def("cltv", param_number, &cltv,
                                   cmd->ld->config.cltv_final),
                         p_opt_def("deschashonly", param_bool, &hashonly, false),
                         p_opt("dev-routes", param_array, &dev_routes),
                         NULL))
                return command_param_failed();

        if (dev_routes && !cmd->ld->developer)
                return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
                                    "dev-routes requires --developer");

        if (strlen(info->label->s) > inv_max_label_len) {
                return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
                                    "Label '%s' over %zu bytes", info->label->s, inv_max_label_len);
        }

        if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) {
                return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
                                    "Descriptions greater than %d bytes "
                                    "not yet supported "
                                    "(description length %zu)",
                                    BOLT11_FIELD_BYTE_LIMIT,
                                    strlen(desc_val));
        }

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

        info->custom_fallbacks = false;
        if (fallbacks) {
                info->custom_fallbacks = true;
                if (cmd->ld->unified_invoices) {
                        log_info(cmd->ld->log,
                        "WARNING: Not tracking on-chain payments "
                        "for custom fallback addresses");
                }
                size_t i;
                const jsmntok_t *t;

                fallback_scripts = tal_arr(cmd, const u8 *, fallbacks->size);
                json_for_each_arr(i, t, fallbacks) {
                        struct command_result *r;

                        r = parse_fallback(cmd, buffer, t, &fallback_scripts[i]);
                        if (r)
                                return r;
                }
        } else if (cmd->ld->unified_invoices) {
                struct pubkey pubkey;
                const u8 *p2tr;

                fallback_scripts = tal_arr(cmd, const u8 *, 1);

                if (!newaddr_inner(cmd, &pubkey, ADDR_P2TR))
                        return command_fail(cmd, LIGHTNINGD, "Keys exhausted ");

                p2tr = scriptpubkey_p2tr(fallback_scripts, &pubkey);
                fallback_scripts[0] = p2tr;
        }

        if (preimage)
                info->payment_preimage = *preimage;
        else
                /* Generate random secret preimage. */
                randombytes_buf(&info->payment_preimage,
                                sizeof(info->payment_preimage));
        /* Generate preimage hash. */
        sha256(&rhash, &info->payment_preimage, sizeof(info->payment_preimage));
        /* Generate payment secret. */
        invoice_secret(&info->payment_preimage, &payment_secret);

        info->b11 = new_bolt11(info, msatoshi_val);
        info->b11->chain = chainparams;
        info->b11->timestamp = time_now().ts.tv_sec;
        info->b11->payment_hash = rhash;
        info->b11->receiver_id = cmd->ld->id;
        info->b11->min_final_cltv_expiry = *cltv;
        info->b11->expiry = *expiry;
        info->b11->description = tal_steal(info->b11, desc_val);
        /* BOLT #11:
         * * `h` (23): `data_length` 52. 256-bit description of purpose of payment (SHA256).
         *...
         * A writer:
         *...
         *    - MUST include either exactly one `d` or exactly one `h` field.
         */
        if (*hashonly) {
                info->b11->description_hash = tal(info->b11, struct sha256);
                sha256(info->b11->description_hash, desc_val, strlen(desc_val));
        } else
                info->b11->description_hash = NULL;
        info->b11->payment_secret = tal_dup(info->b11, struct secret,
                                      &payment_secret);
        info->b11->features = tal_dup_talarr(info->b11, u8,
                                       cmd->ld->our_features
                                       ->bits[BOLT11_FEATURE]);

        info->b11->routes = unpack_routes(info->b11, buffer, dev_routes);

        if (fallback_scripts)
                info->b11->fallbacks = tal_steal(info->b11, fallback_scripts);

        /* We can't generate routehints without listincoming. */
        plugin = find_plugin_for_command(cmd->ld, "listincoming");
        if (!plugin) {
                return invoice_complete(info, true,
                            false, false, false, false, false);
        }

        req = jsonrpc_request_start(info, "listincoming",
                                    cmd->id, plugin->non_numeric_ids,
                                    command_log(cmd),
                                    NULL, listincoming_done,
                                    info);
        jsonrpc_request_end(req);
        plugin_request_send(plugin, req);
        return command_still_pending(cmd);
}

listincoming_done

listincoming_done in lightning:lightningd/invoice.c:

static void listincoming_done(const char *buffer,
                              const jsmntok_t *toks,
                              const jsmntok_t *idtok UNUSED,
                              struct invoice_info *info)
{
        struct command_result *ret;
        bool warning_mpp, warning_capacity, warning_deadends, warning_offline, warning_private_unused;

        ret = add_routehints(info, buffer, toks,
                             &warning_mpp,
                             &warning_capacity,
                             &warning_deadends,
                             &warning_offline,
                             &warning_private_unused);
        if (ret)
                return;

        invoice_complete(info,
                         false,
                         warning_mpp,
                         warning_capacity,
                         warning_deadends,
                         warning_offline,
                         warning_private_unused);
}

invoice_complete

invoice_complete in lightning:lightningd/invoice.c:

static struct command_result *
invoice_complete(struct invoice_info *info,
                 bool warning_no_listincoming,
                 bool warning_mpp,
                 bool warning_capacity,
                 bool warning_deadends,
                 bool warning_offline,
                 bool warning_private_unused)
{
        struct json_stream *response;
        u64 inv_dbid;
        char *b11enc;
        const struct invoice_details *details;
        struct secret payment_secret;
        struct wallet *wallet = info->cmd->ld->wallet;

        b11enc = bolt11_encode(info, info->b11, false,
                               hsm_sign_b11, info->cmd->ld);

        /* Check duplicate preimage (unlikely unless they specified it!) */
        if (invoices_find_by_rhash(wallet->invoices,
                                   &inv_dbid, &info->b11->payment_hash)) {
                return command_fail(info->cmd,
                                    INVOICE_PREIMAGE_ALREADY_EXISTS,
                                    "preimage already used");
        }

        if (!invoices_create(wallet->invoices,
                             &inv_dbid,
                             info->b11->msat,
                             info->label,
                             info->b11->expiry,
                             b11enc,
                             info->b11->description,
                             info->b11->features,
                             &info->payment_preimage,
                             &info->b11->payment_hash,
                             NULL)) {
                return command_fail(info->cmd, INVOICE_LABEL_ALREADY_EXISTS,
                                    "Duplicate label '%s'",
                                    info->label->s);
        }

        if (info->cmd->ld->unified_invoices && info->b11->fallbacks && !info->custom_fallbacks) {
                for (size_t i = 0; i < tal_count(info->b11->fallbacks); i++) {
                        const u8 *fallback_script = info->b11->fallbacks[i];
                        invoices_create_fallback(wallet->invoices, inv_dbid, fallback_script);
                }
        }

        /* Get details */
        details = invoices_get_details(info, wallet->invoices, inv_dbid);

        response = json_stream_success(info->cmd);
        json_add_sha256(response, "payment_hash", &details->rhash);
        json_add_u64(response, "expires_at", details->expiry_time);
        json_add_string(response, "bolt11", details->invstring);
        invoice_secret(&details->r, &payment_secret);
        json_add_secret(response, "payment_secret", &payment_secret);
        json_add_u64(response, "created_index", details->created_index);

        notify_invoice_creation(info->cmd->ld, info->b11->msat,
                                info->payment_preimage, info->label);

        if (warning_no_listincoming)
                json_add_string(response, "warning_listincoming",
                                          "No listincoming command available, cannot add routehints to invoice");
        if (warning_mpp)
                json_add_string(response, "warning_mpp",
                                          "The invoice is only payable by MPP-capable payers.");
        if (warning_capacity)
                json_add_string(response, "warning_capacity",
                                          "Insufficient incoming channel capacity to pay invoice");

        if (warning_deadends)
                json_add_string(response, "warning_deadends",
                                          "Insufficient incoming capacity, once dead-end peers were excluded");

        if (warning_offline)
                json_add_string(response, "warning_offline",
                                          "Insufficient incoming capacity, once offline peers were excluded");

        if (warning_private_unused)
                json_add_string(response, "warning_private_unused",
                                          "Insufficient incoming capacity, once private channels were excluded (try exposeprivatechannels=true?)");

        if (info->cmd->ld->unified_invoices && info->custom_fallbacks)
                json_add_string(response, "warning_custom_fallbacks",
                                          "WARNING: Not tracking on-chain payments for custom fallback addresses");

        return command_success(info->cmd, response);
}

add_routehints

add_routehints in lightning:lightningd/invoice.c:

/* Add routehints based on listincoming results: NULL means success. */
static struct command_result *
add_routehints(struct invoice_info *info,
               const char *buffer,
               const jsmntok_t *toks,
               bool *warning_mpp,
               bool *warning_capacity,
               bool *warning_deadends,
               bool *warning_offline,
               bool *warning_private_unused)
{
        const struct chanhints *chanhints = info->chanhints;
        bool node_unpublished;
        struct amount_msat avail_capacity, deadend_capacity, offline_capacity,
                private_capacity;
        struct routehint_candidate *candidates;
        struct amount_msat total, needed;

        /* Dev code can force routes. */
        if (info->b11->routes) {
               *warning_mpp = *warning_capacity = *warning_deadends
                       = *warning_offline = *warning_private_unused
                       = false;
                return NULL;
        }

        candidates = routehint_candidates(tmpctx, info->cmd->ld,
                                          buffer, toks,
                                          chanhints ? &chanhints->expose_all_private : NULL,
                                          chanhints ? chanhints->hints : NULL,
                                          &node_unpublished,
                                          &avail_capacity,
                                          &private_capacity,
                                          &deadend_capacity,
                                          &offline_capacity);

        /* If they told us to use scids and we couldn't, fail. */
        if (tal_count(candidates) == 0
            && chanhints && tal_count(chanhints->hints) != 0) {
                return command_fail(info->cmd,
                                    INVOICE_HINTS_GAVE_NO_ROUTES,
                                    "None of those hints were suitable local channels");
        }

        needed = info->b11->msat ? *info->b11->msat : AMOUNT_MSAT(1);

        /* If we are not completely unpublished, try with reservoir
         * sampling first.
         *
         * Why do we not do this if we are completely unpublished?
         * Because it is possible that multiple invoices will, by
         * chance, select the same channel as routehint.
         * This single channel might not be able to accept all the
         * incoming payments on all the invoices generated.
         * If we were published, that is fine because the payer can
         * fall back to just attempting to route directly.
         * But if we were unpublished, the only way for the payer to
         * reach us would be via the routehints we provide, so we
         * should make an effort to avoid overlapping incoming
         * channels, which is done by select_inchan_mpp.
         */
        if (!node_unpublished)
                info->b11->routes = select_inchan(info->b11,
                                                  info->cmd->ld,
                                                  needed,
                                                  candidates);

        /* If we are completely unpublished, or if the above reservoir
         * sampling fails, select channels by round-robin.  */
        if (tal_count(info->b11->routes) == 0) {
                info->b11->routes = select_inchan_mpp(info->b11,
                                                      info->cmd->ld,
                                                      needed,
                                                      candidates);
                *warning_mpp = (tal_count(info->b11->routes) > 1);
        } else {
                *warning_mpp = false;
        }

        log_debug(info->cmd->ld->log, "needed = %s, avail_capacity = %s, private_capacity = %s, offline_capacity = %s, deadend_capacity = %s",
            type_to_string(tmpctx, struct amount_msat, &needed),
            type_to_string(tmpctx, struct amount_msat, &avail_capacity),
            type_to_string(tmpctx, struct amount_msat, &private_capacity),
            type_to_string(tmpctx, struct amount_msat, &offline_capacity),
            type_to_string(tmpctx, struct amount_msat, &deadend_capacity));

        if (!amount_msat_add(&total, avail_capacity, offline_capacity)
            || !amount_msat_add(&total, total, deadend_capacity)
            || !amount_msat_add(&total, total, private_capacity))
                fatal("Cannot add %s + %s + %s + %s",
                      type_to_string(tmpctx, struct amount_msat,
                                     &avail_capacity),
                      type_to_string(tmpctx, struct amount_msat,
                                     &offline_capacity),
                      type_to_string(tmpctx, struct amount_msat,
                                     &deadend_capacity),
                      type_to_string(tmpctx, struct amount_msat,
                                     &private_capacity));

        /* If we literally didn't have capacity at all, warn. */
        *warning_capacity = amount_msat_greater_eq(needed, total);

        /* We only warn about these if we didn't have capacity and
         * they would have helped. */
        *warning_offline = false;
        *warning_deadends = false;
        *warning_private_unused = false;
        if (amount_msat_greater(needed, avail_capacity)) {
                struct amount_msat tot;

                /* We didn't get enough: would offline have helped? */
                if (!amount_msat_add(&tot, avail_capacity, offline_capacity))
                        abort();
                if (amount_msat_greater_eq(tot, needed)) {
                        *warning_offline = true;
                        goto done;
                }

                /* Hmm, what about deadends? */
                if (!amount_msat_add(&tot, tot, deadend_capacity))
                        abort();
                if (amount_msat_greater_eq(tot, needed)) {
                        *warning_deadends = true;
                        goto done;
                }

                /* What about private channels? */
                if (!amount_msat_add(&tot, tot, private_capacity))
                        abort();
                if (amount_msat_greater_eq(tot, needed)) {
                        *warning_private_unused = true;
                        goto done;
                }
        }

 done:
        return NULL;
}

routehint_candidates

routehint_candidates in lightning:lightningd/routehint.c:

struct routehint_candidate *
routehint_candidates(const tal_t *ctx,
                     struct lightningd *ld,
                     const char *buf,
                     const jsmntok_t *toks,
                     const bool *expose_all_private,
                     const struct short_channel_id *hints,
                     bool *none_public,
                     struct amount_msat *avail_capacity,
                     struct amount_msat *private_capacity,
                     struct amount_msat *deadend_capacity,
                     struct amount_msat *offline_capacity)
{
        struct routehint_candidate *candidates, *privcandidates;
        const jsmntok_t *t, *arr;
        size_t i;

        log_debug(ld->log, "routehint: %.*s",
                  json_tok_full_len(toks),
                  json_tok_full(buf, toks));

        /* We get the full JSON, including result. */
        t = json_get_member(buf, toks, "result");
        if (!t)
                fatal("Missing result from listincoming: %.*s",
                      json_tok_full_len(toks),
                      json_tok_full(buf, toks));
        arr = json_get_member(buf, t, "incoming");

        candidates = tal_arr(ctx, struct routehint_candidate, 0);
        privcandidates = tal_arr(tmpctx, struct routehint_candidate, 0);
        *none_public = true;
        *deadend_capacity = AMOUNT_MSAT(0);
        *offline_capacity = AMOUNT_MSAT(0);
        *avail_capacity = AMOUNT_MSAT(0);
        *private_capacity = AMOUNT_MSAT(0);

        /* We combine the JSON output which knows the peers' details,
         * with our internal information */
        json_for_each_arr(i, t, arr) {
                struct amount_msat capacity;
                const char *err;
                struct routehint_candidate candidate;
                struct amount_msat fee_base, htlc_max;
                struct route_info *r;
                struct peer *peer;
                bool is_public;

                r = tal(tmpctx, struct route_info);

                err = json_scan(tmpctx, buf, t,
                                "{id:%"
                                ",short_channel_id:%"
                                ",fee_base_msat:%"
                                ",fee_proportional_millionths:%"
                                ",cltv_expiry_delta:%"
                                ",htlc_max_msat:%"
                                ",incoming_capacity_msat:%"
                                "}",
                                JSON_SCAN(json_to_node_id, &r->pubkey),
                                JSON_SCAN(json_to_short_channel_id,
                                          &r->short_channel_id),
                                JSON_SCAN(json_to_msat, &fee_base),
                                JSON_SCAN(json_to_u32,
                                          &r->fee_proportional_millionths),
                                JSON_SCAN(json_to_u16, &r->cltv_expiry_delta),
                                JSON_SCAN(json_to_msat, &htlc_max),
                                JSON_SCAN(json_to_msat, &capacity));

                if (err) {
                        fatal("Invalid return from listincoming (%s): %.*s",
                              err,
                              json_tok_full_len(toks),
                              json_tok_full(buf, toks));
                }

                /* Do we know about this peer? */
                peer = peer_by_id(ld, &r->pubkey);
                if (!peer) {
                        log_debug(ld->log, "%s: unknown peer",
                type_to_string(tmpctx,
                               struct node_id,
                               &r->pubkey));
                        continue;
                }

                /* Check channel is in CHANNELD_NORMAL or CHANNELD_AWAITING_SPLICE */
                candidate.c = find_channel_by_scid(peer, &r->short_channel_id);

                /* Try seeing if we should be using a remote alias
                 * instead. The `listpeers` result may have returned
                 * the REMOTE alias, because it is the only scid we
                 * have, and it is mandatory once the channel is in
                 * CHANNELD_NORMAL or CHANNELD_AWAITING_SPLICE. */
                if (!candidate.c)
                        candidate.c = find_channel_by_alias(peer, &r->short_channel_id, REMOTE);

                if (!candidate.c) {
                        log_debug(ld->log, "%s: channel not found in peer %s",
                                  type_to_string(tmpctx,
                                                 struct short_channel_id,
                                                 &r->short_channel_id),
                                  type_to_string(tmpctx,
                                                 struct node_id,
                                                 &r->pubkey));
                                          continue;
                }

                if (!channel_state_can_add_htlc(candidate.c->state)) {
                        log_debug(ld->log, "%s: abnormal channel",
                                  type_to_string(tmpctx,
                                                 struct short_channel_id,
                                                 &r->short_channel_id));
                                          continue;
                }

                /* FIXME: we don't actually check htlc_minimum_msat! */

                /* If they set an htlc_maximum_msat, consider that the
                 * capacity ceiling.  We *could* do multiple HTLCs,
                 * but presumably that would defeat the spirit of the
                 * limit anyway */
                /* FIXME: Present max capacity of multiple channels? */
                candidate.capacity = channel_amount_receivable(candidate.c);
                if (amount_msat_greater(candidate.capacity, htlc_max))
                        candidate.capacity = htlc_max;

                /* Now we can tell if it's public.  If so (even if it's otherwise
                 * unusable), we *don't* expose private channels! */
                is_public = (candidate.c->channel_flags
                 & CHANNEL_FLAGS_ANNOUNCE_CHANNEL);

                if (is_public)
                        *none_public = false;

                /* If they explicitly say to expose all private ones, consider
                 * it public. */
                if (expose_all_private != NULL && *expose_all_private)
                        is_public = true;

                r->fee_base_msat = fee_base.millisatoshis; /* Raw: route_info */
                /* Could wrap: if so ignore */
                if (!amount_msat_eq(amount_msat(r->fee_base_msat), fee_base)) {
                        log_debug(ld->log,
                                  "Peer charging insane fee %.*s; ignoring",
                                  json_tok_full_len(t),
                                  json_tok_full(buf, t));
                                          continue;
                }

                /* Consider only hints they gave */
                if (hints) {
                        log_debug(ld->log, "We have hints!");
                        /* Allow specification by alias, too */
                        if (!scid_in_arr(hints, &r->short_channel_id)
                            && (!candidate.c->alias[REMOTE]
                                || !scid_in_arr(hints, candidate.c->alias[REMOTE]))) {
                                log_debug(ld->log, "scid %s not in hints",
                                          type_to_string(tmpctx,
                                                         struct short_channel_id,
                                                         &r->short_channel_id));
                                continue;
                        }
                        /* If they give us a hint, we use even if capacity 0 */
                } else if (amount_msat_eq(capacity, AMOUNT_MSAT(0))) {
                        log_debug(ld->log, "%s: deadend",
                type_to_string(tmpctx,
                               struct short_channel_id,
                               &r->short_channel_id));
                        if (!amount_msat_add(deadend_capacity,
                                             *deadend_capacity,
                                             candidate.capacity))
                                fatal("Overflow summing deadend capacity!");
                        continue;
                }

                /* Is it offline? */
                if (candidate.c->owner == NULL) {
                        log_debug(ld->log, "%s: offline",
                                  type_to_string(tmpctx,
                                                 struct short_channel_id,
                                                 &r->short_channel_id));
                        if (!amount_msat_add(offline_capacity,
                                             *offline_capacity,
                                             candidate.capacity))
                                fatal("Overflow summing offline capacity!");
                        continue;
                }

                /* BOLT-channel-type #2:
                 *   - if `channel_type` has `option_scid_alias` set:
                 *       - MUST NOT use the real `short_channel_id` in
                 *         BOLT 11 `r` fields.
                 */
                /* FIXME: We don't remember the type explicitly, so
                 * we just assume all private channels negotiated since
                 * we had alias support want this. */

                /* Note explicit flag test here: if we're told to expose all
                 * private channels, then "is_public" is forced true */
                if (!(candidate.c->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL)
                    && candidate.c->alias[REMOTE]) {
                        r->short_channel_id = *candidate.c->alias[REMOTE];
                }

                /* OK, finish it and append to one of the arrays. */
                if (is_public) {
                        log_debug(ld->log, "%s: added to public",
                                  type_to_string(tmpctx,
                                                 struct short_channel_id,
                                                 &r->short_channel_id));
                        candidate.r = tal_steal(candidates, r);
                        tal_arr_expand(&candidates, candidate);
                        if (!amount_msat_add(avail_capacity,
                                             *avail_capacity,
                                             candidate.capacity)) {
                                fatal("Overflow summing pub capacities %s + %s",
                                      type_to_string(tmpctx, struct amount_msat,
                                                     avail_capacity),
                                      type_to_string(tmpctx, struct amount_msat,
                                                     &candidate.capacity));
                        }
                } else {
                        log_debug(ld->log, "%s: added to private",
                                  type_to_string(tmpctx,
                                                 struct short_channel_id,
                                                 &r->short_channel_id));
                        candidate.r = tal_steal(privcandidates, r);
                        tal_arr_expand(&privcandidates, candidate);
                        if (!amount_msat_add(private_capacity,
                                             *private_capacity,
                                             candidate.capacity)) {
                                fatal("Overflow summing priv capacities %s + %s",
                                      type_to_string(tmpctx, struct amount_msat,
                                                     private_capacity),
                                      type_to_string(tmpctx, struct amount_msat,
                                                     &candidate.capacity));
                        }
                }
        }

        /* By default, only consider private channels if there are
         * no public channels *at all* */
        if (expose_all_private == NULL
            && tal_count(candidates) == 0 && *none_public) {
                log_debug(ld->log, "No publics: using private channels");
                tal_free(candidates);
                candidates = tal_steal(ctx, privcandidates);
                *avail_capacity = *private_capacity;
                /* This reflects *unused* private capacity. */
                *private_capacity = AMOUNT_MSAT(0);
        }

        return candidates;
}

select_inchan

select_inchan in lightning:lightningd/invoice.c:

/*
 * From array of incoming channels [inchan], find suitable ones for
 * a payment-to-us of [amount_needed], using criteria:
 * 1. Channel's peer is known, in state CHANNELD_NORMAL and is online.
 * 2. Channel's peer capacity to pay us is sufficient.
 *
 * Then use weighted reservoir sampling, which makes probing channel balances
 * harder, to choose one channel from the set of suitable channels. It favors
 * channels that have less balance on our side as fraction of their capacity.
 */
static struct route_info **select_inchan(const tal_t *ctx,
                                         struct lightningd *ld,
                                         struct amount_msat amount_needed,
                                         const struct routehint_candidate
                                         *candidates)
{
        /* BOLT11 struct wants an array of arrays (can provide multiple routes) */
        struct route_info **r = NULL;
        double total_weight = 0.0;

        /* Collect suitable channels and assign each a weight.  */
        for (size_t i = 0; i < tal_count(candidates); i++) {
                struct amount_msat excess, capacity;
                struct amount_sat cumulative_reserve;
                double excess_frac;

                /* Does the peer have sufficient balance to pay us,
                 * even after having taken into account their reserve? */
                if (!amount_msat_sub(&excess, candidates[i].capacity,
                                     amount_needed))
                        continue;

                /* Channel balance as seen by our node:

                        |<----------------- capacity ----------------->|
                        .                                              .
                        .             |<------------------ their_msat -------------------->|
                        .             |                                .                   |
                        .             |<----- capacity_to_pay_us ----->|<- their_reserve ->|
                        .             |                                |                   |
                        .             |<- amount_needed --><- excess ->|                   |
                        .             |                                |                   |
                |-------|-------------|--------------------------------|-------------------|
                0       ^             ^                                ^                funding
                   our_reserve     our_msat        */

                /* Find capacity and calculate its excess fraction */
                if (!amount_sat_add(&cumulative_reserve,
                                    candidates[i].c->our_config.channel_reserve,
                                    candidates[i].c->channel_info.their_config.channel_reserve)
                        || !amount_sat_to_msat(&capacity, candidates[i].c->funding_sats)
                        || !amount_msat_sub_sat(&capacity, capacity, cumulative_reserve)) {
                        log_broken(ld->log, "Channel %s capacity overflow!",
                                        type_to_string(tmpctx, struct short_channel_id, candidates[i].c->scid));
                        continue;
                }

                /* We don't want a 0 probability if 0 excess; it might be the
                 * only one!  So bump it by 1 msat */
                if (!amount_msat_add(&excess, excess, AMOUNT_MSAT(1))) {
                        log_broken(ld->log, "Channel %s excess overflow!",
                                   type_to_string(tmpctx,
                                                  struct short_channel_id,
                                                  candidates[i].c->scid));
                        continue;
                }
                excess_frac = amount_msat_ratio(excess, capacity);

                if (random_select(excess_frac, &total_weight)) {
                        tal_free(r);
                        r = tal_arr(ctx, struct route_info *, 1);
                        r[0] = tal_dup(r, struct route_info, candidates[i].r);
                }
        }

        return r;
}

log_debug

log_debug in lightning:lightningd/log.h:

#define log_debug(logger, ...) log_((logger), LOG_DBG, NULL, false, __VA_ARGS__)
#define log_info(logger, ...) log_((logger), LOG_INFORM, NULL, false, __VA_ARGS__)
#define log_unusual(logger, ...) log_((logger), LOG_UNUSUAL, NULL, true, __VA_ARGS__)
#define log_broken(logger, ...) log_((logger), LOG_BROKEN, NULL, true, __VA_ARGS__)

type_to_string

type_to_string in lightning:common/type_to_string.h:

#define type_to_string(ctx, type, ptr)                                 \
        type_to_string_((ctx), stringify(type),                        \
                        ((void)sizeof((ptr) == (type *)NULL),          \
                        ((union printable_types)((const type *)ptr))))

const char *type_to_string_(const tal_t *ctx, const char *typename,
                            union printable_types u);

printable_types

printable_types in lightning:common/type_to_string.h:

/* This must match the type_to_string_ cases. */
union printable_types {
        const struct pubkey *pubkey;
        const struct node_id *node_id;
        const struct bitcoin_txid *bitcoin_txid;
        const struct bitcoin_blkid *bitcoin_blkid;
        const struct bitcoin_outpoint *bitcoin_outpoint;
        const struct sha256 *sha256;
        const struct sha256_double *sha256_double;
        const struct ripemd160 *ripemd160;
        const struct bitcoin_tx *bitcoin_tx;
        const struct htlc *htlc;
        const struct preimage *preimage;
        const struct channel_oneside *channel_oneside;
        const struct wireaddr *wireaddr;
        const struct wireaddr_internal *wireaddr_internal;
        const secp256k1_pubkey *secp256k1_pubkey;
        const struct channel_id *channel_id;
        const struct short_channel_id *short_channel_id;
        const struct short_channel_id_dir *short_channel_id_dir;
        const struct secret *secret;
        const struct privkey *privkey;
        const secp256k1_ecdsa_signature *secp256k1_ecdsa_signature;
        const struct bitcoin_signature *bitcoin_signature;
        const struct bip340sig *bip340sig;
        const struct channel *channel;
        const struct amount_msat *amount_msat;
        const struct amount_sat *amount_sat;
        const struct fee_states *fee_states;
        const struct height_states *height_states;
        const char *charp_;
        const struct wally_psbt *wally_psbt;
        const struct wally_tx *wally_tx;
};

Resources