Core Lightning implementation of BOLT #11 invoices - part 2
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:
this gives us a tool for understanding many parts of Core Lightning,
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:
type
(5 bits)data_length
(10 bits, big-endian)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 oner
fieldpubkey
(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:
Helpers for outputting JSON results into a membuf (lightning:common/json_stream.h).
Helpers for use with param parsing (lightning:common/json_param.h).
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;
};