Core Lightning implementation of BOLT #11 invoices - part 1
In this live we see how to write JSON RPC commands in Core Lightning. Specifically, we write 3 JSON RPC commands foo
, my-invoice
and my-commands
. The foo
command takes no argument and returns {"foo": {"bar":"baz"}}
. We also modify it to return the node id. Then we look at the invoice
command which takes arguments and write my-invoice
command that takes arguments. Finally, we write my-commands
command which lists the commands registered in JSON RPC interface.
Transcript with corrections and improvements
Hi everybody I hope you're doing well, let's get started this live session about Lightning Network invoices.
What I want to do today and in the next few months is to talk about BOLT #11 invoices, specifically how they are implemented in Core Lightning.
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"
}
What happens when we run the previous command is that we send a JSON
RPC request like the following to the lightning-rpc
socket file
{
"jsonrpc": "2.0",
"id": "1",
"method": "invoice",
"params": ["10000", "label-1", "description-1"]
}
and lightningd
handles it and returns the JSON RPC response with the
payload presented above.
The goals of the next live coding sessions is:
to understand how this JSON output is generated,
what are BOLT #11 specificities and
what are Core Lightning specificities.
We've seen in other lives how to add JSON RPC commands to CLN using plugins.
Today, we are going to see how to write JSON RPC commands in Core Lightning.
Specifically, we'll write 3 JSON RPC commands foo
, my-invoice
and
my-commands
. The foo
command takes no argument and returns {"foo":
{"bar":"baz"}}
. We'll also modify it to return the node id. Then
we'll look at the invoice
command which takes arguments and write
my-invoice
command that takes arguments. Finally, we'll write
my-commands
command which lists the commands registered in JSON RPC
interface.
Compiling CLN with rust plugin disabled
In the lightning
repository checked out at v23.08.1
(the latest
release), we run the configure
file, clean the repository from
previous builds (if any) 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.08.1)]
$ ./configure --disable-rust
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make clean
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make -j4
CC: cc -DBINTOPKGLIBEXECDIR="../libexec/c-lightning" -Wall -Wundef -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes -Wold-style-definition -Werror -Wno-maybe-uninitialized -Wshadow=local -std=gnu11 -g -fstack-protector-strong -Og -I ccan -I external/libwally-core/include/ -I external/libwally-core/src/secp256k1/include/ -I external/jsmn/ -I external/libbacktrace/ -I external/gheap/ -I external/build-x86_64-linux-gnu/libbacktrace-build -I external/libsodium/src/libsodium/include -I external/libsodium/src/libsodium/include/sodium -I external/build-x86_64-linux-gnu/libsodium-build/src/libsodium/include -I . -I/usr/local/include -I/usr/include/postgresql -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS -DCOMPAT_V052=1 -DCOMPAT_V060=1 -DCOMPAT_V061=1 -DCOMPAT_V062=1 -DCOMPAT_V070=1 -DCOMPAT_V072=1 -DCOMPAT_V073=1 -DCOMPAT_V080=1 -DCOMPAT_V081=1 -DCOMPAT_V082=1 -DCOMPAT_V090=1 -DCOMPAT_V0100=1 -DCOMPAT_V0121=1 -DBUILD_ELEMENTS=1 -c -o
LD: cc -Og config.vars -Lexternal/build-x86_64-linux-gnu -lwallycore -lsecp256k1 -ljsmn -lbacktrace -lsodium -L/usr/local/include -lm -lsqlite3 -lz -L/usr/lib/x86_64-linux-gnu -lpq -o
...
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
.
peer_control.c, invoice.c and jsonrpc.c files
The 3 commands foo
, my-invoice
and my-commands
that we are going to
add to the JSON RPC interface will be written respectively in the
files:
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);
Some helpers to write commands:
Helpers for outputting JSON results into a membuf (lightning:common/json_stream.h),
Helpers for use with param parsing (lightning:common/json_param.h).
The statement AUTODATA(...);
is used to declare JSON RPC commands when
the first argument is json_command
as above.
Those JSON RPC commands are collected before the main
function of
lightningd
binary runs. Then, in that main
function a call to
jsonrpc_setup
function registers those commands to the JSON RPC
interface.
Let's list all those declarations using rg
utility:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ rg 'AUTODATA\(json_command'
lightningd/plugin_control.c
325:AUTODATA(json_command, &plugin_control_command);
lightningd/ping.c
95:AUTODATA(json_command, &ping_command);
lightningd/dual_open_control.c
3544:AUTODATA(json_command, &queryrates_command);
3585:AUTODATA(json_command, &openchannel_init_command);
3586:AUTODATA(json_command, &openchannel_update_command);
3587:AUTODATA(json_command, &openchannel_signed_command);
3588:AUTODATA(json_command, &openchannel_bump_command);
3589:AUTODATA(json_command, &openchannel_abort_command);
lightningd/peer_control.c
2110:AUTODATA(json_command, &listpeers_command);
2163:AUTODATA(json_command, &staticbackup_command);
2223:AUTODATA(json_command, &listpeerchannels_command);
2428:AUTODATA(json_command, &disconnect_command);
2553:AUTODATA(json_command, &getinfo_command);
2659:AUTODATA(json_command, &waitblockheight_command);
2912:AUTODATA(json_command, &setchannel_command);
2976:AUTODATA(json_command, &dev_sign_last_tx);
3017:AUTODATA(json_command, &dev_fail_command);
3076:AUTODATA(json_command, &dev_reenable_commit);
3193:AUTODATA(json_command, &dev_forget_channel_command);
lightningd/signmessage.c
115:AUTODATA(json_command, &json_signmessage_cmd);
243:AUTODATA(json_command, &json_checkmessage_cmd);
lightningd/gossip_control.c
487:AUTODATA(json_command, &setleaserates_command);
536:AUTODATA(json_command, &addgossip_command);
565:AUTODATA(json_command, &dev_set_max_scids_encode_size);
608:AUTODATA(json_command, &dev_compact_gossip_store);
635:AUTODATA(json_command, &dev_gossip_set_time);
lightningd/invoice.c
1225:AUTODATA(json_command, &invoice_command);
1353:AUTODATA(json_command, &listinvoices_command);
1434:AUTODATA(json_command, &delinvoice_command);
1460:AUTODATA(json_command, &delexpiredinvoice_command);
1510:AUTODATA(json_command, &waitanyinvoice_command);
1556:AUTODATA(json_command, &waitinvoice_command);
1592:AUTODATA(json_command, &decodepay_command);
1841:AUTODATA(json_command, &createinvoice_command);
1883:AUTODATA(json_command, &preapproveinvoice_command);
1924:AUTODATA(json_command, &preapprovekeysend_command);
1978:AUTODATA(json_command, &signinvoice_command);
lightningd/closed_channel.c
119:AUTODATA(json_command, &listclosedchannels_command);
lightningd/closing_control.c
906:AUTODATA(json_command, &close_command);
lightningd/offer.c
130:AUTODATA(json_command, &createoffer_command);
192:AUTODATA(json_command, &listoffers_command);
231:AUTODATA(json_command, &disableoffer_command);
499:AUTODATA(json_command, &createinvreq_command);
536:AUTODATA(json_command, &payersign_command);
599:AUTODATA(json_command, &listinvoicerequests_command);
639:AUTODATA(json_command, &disableinvoicerequest_command);
lightningd/log.c
1164:AUTODATA(json_command, &getlog_command);
lightningd/jsonrpc.c
177:AUTODATA(json_command, &help_command);
231:AUTODATA(json_command, &stop_command);
315:AUTODATA(json_command, &dev_command);
1516:AUTODATA(json_command, &check_command);
1542:AUTODATA(json_command, ¬ifications_command);
1568:AUTODATA(json_command, &batching_command);
lightningd/channel_control.c
2070:AUTODATA(json_command, &splice_init_command);
2083:AUTODATA(json_command, &splice_update_command);
2091:AUTODATA(json_command, &splice_signed_command);
2143:AUTODATA(json_command, &dev_feerate_command);
2196:AUTODATA(json_command, &dev_quiesce_command);
lightningd/peer_htlcs.c
2989:AUTODATA(json_command, &dev_ignore_htlcs);
3120:AUTODATA(json_command, &listforwards_command);
3185:AUTODATA(json_command, &delforward_command);
3265:AUTODATA(json_command, &listhtlcs_command);
lightningd/configs.c
338:AUTODATA(json_command, &listconfigs_command);
631:AUTODATA(json_command, &setconfig_command);
lightningd/runes.c
399:AUTODATA(json_command, &showrunes_command);
592:AUTODATA(json_command, &creatrune_command);
600:AUTODATA(json_command, &invokerune_command);
701:AUTODATA(json_command, &blacklistrune_command);
709:AUTODATA(json_command, &destroyrune_command);
848:AUTODATA(json_command, &checkrune_command);
lightningd/wait.c
216:AUTODATA(json_command, &wait_command);
lightningd/connect_control.c
257:AUTODATA(json_command, &connect_command);
825:AUTODATA(json_command, &sendcustommsg_command);
837:AUTODATA(json_command, &dev_sendcustommsg_command);
862:AUTODATA(json_command, &dev_suppress_gossip);
884:AUTODATA(json_command, &dev_report_fds);
lightningd/hsm_control.c
283:AUTODATA(json_command, &makesecret_command);
lightningd/chaintopology.c
783:AUTODATA(json_command, &feerates_command);
810:AUTODATA(json_command, &parse_feerate_command);
lightningd/onion_message.c
245:AUTODATA(json_command, &sendonionmessage_command);
357:AUTODATA(json_command, &blindedpath_command);
lightningd/datastore.c
298:AUTODATA(json_command, &datastore_command);
306:AUTODATA(json_command, &deldatastore_command);
314:AUTODATA(json_command, &listdatastore_command);
lightningd/memdump.c
89:AUTODATA(json_command, &dev_memdump_command);
313:AUTODATA(json_command, &dev_memleak_command);
lightningd/opening_control.c
1552:AUTODATA(json_command, &fundchannel_start_command);
1560:AUTODATA(json_command, &fundchannel_cancel_command);
1569:AUTODATA(json_command, &fundchannel_complete_command);
1579:AUTODATA(json_command, &json_commitchan_command);
lightningd/pay.c
1374:AUTODATA(json_command, &sendonion_command);
1636:AUTODATA(json_command, &sendpay_command);
1683:AUTODATA(json_command, &waitsendpay_command);
1770:AUTODATA(json_command, &listsendpays_command);
1878:AUTODATA(json_command, &delpay_command);
1942:AUTODATA(json_command, &createonion_command);
wallet/reservation.c
147:AUTODATA(json_command, &reserveinputs_command);
230:AUTODATA(json_command, &unreserveinputs_command);
655:AUTODATA(json_command, &fundpsbt_command);
840:AUTODATA(json_command, &utxopsbt_command);
wallet/walletrpc.c
189:AUTODATA(json_command, &newaddr_command);
272:AUTODATA(json_command, &listaddrs_command);
428:AUTODATA(json_command, &listfunds_command);
505:AUTODATA(json_command, &dev_rescan_output_command);
610:AUTODATA(json_command, &listtransactions_command);
824:AUTODATA(json_command, &signpsbt_command);
860:AUTODATA(json_command, &setpsbtversion_command);
1022:AUTODATA(json_command, &sendpsbt_command);
Start 2 Lightning nodes running on regtest
Let's start two Lightning nodes running on the Bitcoin regtest
chain
by sourcing the script lightning/contrib/startup_regtest.sh
provided
by CLN repository and running the command start_ln
:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ 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.08.1)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
error code: -35
error message:
Wallet "default" is already loaded.
[1] 815172
[2] 815218
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 lightningd
is
/home/tony/clnlive/lightning/lightningd/lightningd
so we are using the
lightningd
we just compiled.
We can check that l1-cli
is just an alias for lightning-cli
with the
base directory being /tmp/l1-regtest
:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ alias l1-cli
alias l1-cli='/home/tony/clnlive/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
To be sure that we have at least a lightning node running on regtest,
we can call the subcommand getinfo
like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli getinfo
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7",
"alias": "PEEVEDAUTO",
"color": "0370e7",
"num_peers": 0,
"num_pending_channels": 0,
"num_active_channels": 0,
"num_inactive_channels": 0,
"address": [],
"binding": [
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7171
}
],
"version": "v23.08.1",
"blockheight": 1,
"network": "regtest",
"fees_collected_msat": 0,
"lightning-dir": "/tmp/l1-regtest/regtest",
"our_features": {
"init": "08a0000a0269a2",
"node": "88a0000a0269a2",
"channel": "",
"invoice": "02000002024100"
}
}
We can also generate a BOLT #11 invoice like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli invoice 10000 label-1 description-1
{
"payment_hash": "4f36f7251095e4e3b2a940ee509d9686f3ebec598b88cacf3a983038980dbe69",
"expires_at": 1701357720,
"bolt11": "lnbcrt100n1pj47mscsp5ll2yqrk8ksdkzdlwpjylclfpun23y2c52zzjfqzptglqxhjvf00spp5fum0wfgsjhjw8v4fgrh9p8vksme7hmze3wyv4ne6nqcr3xqdhe5sdq4v3jhxcmjd9c8g6t0dcknzxqyjw5qcqp29qxpqysgqwl3awdug7qewzjtvhsjda5ysvysnju4nr2ncwmzhd4kn8czysw4ng5ustwq7qwgy074hehexuxudtn9zkjsu3gryke3pvufhea5cgjcql5z6au",
"payment_secret": "ffd4400ec7b41b6137ee0c89fc7d21e4d5122b1450852480415a3e035e4c4bdf",
"created_index": 1,
"warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}
Write foo JSON RPC command in lightningd/peer_control.c file
In this section we write a foo
command in
lightning:lightningd/peer_control.c file. That command takes no
argument and returns:
{"foo": {"bar":"baz"}}
To do so we take inspiration from the command getinfo
defined in that
same file.
We write that command just after the getinfo
command.
We define a function json_foo
that returns a command_result
struct.
Then we define a json_command
struct foo_command
where the third field
is json_foo
function. Finally, we use AUTODATA
macros to register
foo_command
command:
/* clnlive #17 */
static struct command_result *json_foo(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
if (!param(cmd, buffer, params, NULL))
return command_param_failed();
response = json_stream_success(cmd);
json_add_string(response, "bar", "baz");
return command_success(cmd, response);
}
static const struct json_command foo_command = {
"foo",
"developer",
json_foo,
"foo clnlive #17"
};
AUTODATA(json_command, &foo_command);
We compile:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make -j4
...
Let's define l1-start
alias that let us start the node l1
in
background:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ alias l1-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l1-regtest --daemon'
Now we stop l1
node and restart it with the new compiled lightningd
:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
Now we can call our new foo
JSON RPC command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"bar": "baz"
}
This is almost what we want to do. Now, let's set that object as the
value of a field that we call foo
. To do that we use the functions
json_object_start
and json_object_end
like this:
static struct command_result *json_foo(...)
{
...
response = json_stream_success(cmd);
json_object_start(response, "foo");
json_add_string(response, "bar", "baz");
json_object_end(response);
return command_success(cmd, response);
}
...
We compile:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make -j4
...
Let's restart l1
node with the new compiled lightningd
:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
Now we can call our new foo
JSON RPC command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"foo": {
"bar": "baz"
}
}
Cool!
Finally, let's modify again json_foo
function such that foo
command
returns the node id.
To do that we use the function json_add_node_id
like this:
static struct command_result *json_foo(..)
{
...
response = json_stream_success(cmd);
json_add_node_id(response, "id", &cmd->ld->id);
return command_success(cmd, response);
}
We compile, restart l1
node and call foo
command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7"
}
Write my-invoice JSON RPC command in lightning/invoice.c file
In this section we write a my-invoice
command in
lightning:lightningd/invoice.c file. That command takes two
arguments
amount_msat
which is required anddescription
which is optional
and returns
{
"amount_msat": <amount_msat>,
"description": <description>
}
if description
argument is provided or the following if not:
{
"amount_msat": <amount_msat>
}
To do so we take inspiration from the command invoice
defined in that
same file.
We write that command just after the invoice
command like this:
/* 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|msatoshi", param_positive_msat_or_any, &msatoshi_val),
p_req("description", param_escaped_string, &desc_val),
NULL))
return command_param_failed();
response = json_stream_success(cmd);
return command_success(cmd, response);
}
static const struct json_command my_invoice_command = {
"my-invoice",
"payment",
json_my_invoice,
"my-invoice clnlive #17"};
AUTODATA(json_command, &my_invoice_command);
We compile, restart l1
node and call my-invoice
command like
this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{}
Since we used p_req
macro to set both arguments, if we don't pass the
description
argument to my-invoice
command we get the following error:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000
{
"code": -32602,
"message": "missing required parameter: description"
}
Now let's fill that json response with the amount_msat
field. To do
that we use the functions json_add_amount_msat
like this:
static struct command_result *json_my_invoice(...)
{
...
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);
}
...
Note that we also modified the field name of the first argument and
the function that handles it (param_positive_msat_or_any
->
param_msat
).
We compile, restart l1
node and call my-invoice
command like
this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{
"amount_msat": 10000
}
And because we used param_msat
function to set msatoshi_val
variable
and json_add_amount_msat
, my-invoice
command understands
amount_msat
argument ending with sat
or btc
as we see below:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10sat description
{
"amount_msat": 10000
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 1btc description
{
"amount_msat": 100000000000
}
Finally, we make the description
argument optional. To do that we
use p_opt
macros (instead of p_req
) like this:
static struct command_result *json_my_invoice(...)
{
...
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);
}
We compile, restart l1
node and call my-invoice
command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{
"amount_msat": 10000,
"description": "description"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000
{
"amount_msat": 10000
}
Write my-commands JSON RPC command in lightningd/jsonrpc.c file
In this section we write a my-commands
command in
lightning:lightningd/jsonrpc.c file. That command takes no arguments
and returns the following json object
{
"commands": [
"cmd-1",
"cmd-2",
...
]
}
which list in the commands
field all the registered commands by
lightningd
.
We define my-commands
just after the help
command like this where
cmd->ld->jsonrpc->commands
is an array of all the JSON RPC commands
registered in lightningd
and we return only the name
field of the
first one:
/* clnlive #17 */
static struct command_result *json_my_commands(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
struct json_command **commands;
if (!param(cmd, buffer, params, NULL))
return command_param_failed();
commands = cmd->ld->jsonrpc->commands;
response = json_stream_success(cmd);
json_add_string(response, "command", commands[0]->name);
return command_success(cmd, response);
}
static const struct json_command my_commands_command = {
"my-commands",
"developer",
json_my_commands,
"foo clnlive #17"
};
AUTODATA(json_command, &my_commands_command);
We compile, restart l1
node and call my-commands
command like this
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"command": "feerates"
}
and we see that feerate
is the first listed command in
cmd->ld->jsonrpc->commands
array.
Now we modify json_my_commands
a bit to return the first listed
command in a array. To do that we use the functions json_array_start
and json_array_end
like this:
static struct command_result *json_my_commands(...)
{
...
response = json_stream_success(cmd);
json_array_start(response, "commands");
json_add_string(response, NULL, commands[0]->name);
json_array_end(response);
return command_success(cmd, response);
}
We compile, restart l1
node and call my-commands
command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"feerates"
]
}
Interesting but we want to list all the JSON RCP commands registered
in lightningd
.
To do this we loop over cmd->ld->jsonrpc->commands
array using
tal_count
macro to get the number of elements of that array like this:
static struct command_result *json_my_commands(...)
{
...
response = json_stream_success(cmd);
json_array_start(response, "commands");
for (size_t i = 0; i < tal_count(commands); i++) {
json_add_string(response, NULL, commands[i]->name);
}
json_array_end(response);
return command_success(cmd, response);
}
We compile, restart l1
node and we list all the JSON RPC commands
registered in lightningd
by calling my-commands
command like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"feerates",
"parsefeerate",
"splice_init",
"splice_update",
"splice_signed",
"close",
...
"bkpr-dumpincomecsv",
"bkpr-channelsapy"
]
}
We are almost down playing with that command.
Now we filter those commands and list only the one with the plugin
category.
Note that category
in json_command
struct has been changed in CLN
v0.7.1 and can be "bitcoin"
, "channels"
, "network"
, "payment"
,
"plugins"
, "utility"
, "developer"
for native commands, or any other
new category set by a plugin.
So, we can do that filtering like this using the macro streq
:
static struct command_result *json_my_commands(...)
{
...
response = json_stream_success(cmd);
json_array_start(response, "commands");
for (size_t i = 0; i < tal_count(commands); i++) {
if (streq(commands[i]->category, "plugin"))
json_add_string(response, NULL, commands[i]->name);
}
json_array_end(response);
return command_success(cmd, response);
}
We compile, restart l1
node and we list only the JSON RPC commands
with the plugin
category by calling my-commands
like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"plugin",
"emergencyrecover",
"restorefrompeer",
"keysend",
"renepaystatus",
"renepay",
"getrawblockbyheight",
"getchaininfo",
"estimatefees",
"sendrawtransaction",
"getutxout",
"commando",
"commando-rune",
"commando-listrunes",
"commando-blacklist",
"getroute",
"listchannels",
"listnodes",
"listincoming",
"offer",
"invoicerequest",
"decode",
"paystatus",
"listpays",
"pay",
"txprepare",
"txdiscard",
"txsend",
"withdraw",
"upgradewallet",
"autocleaninvoice",
"autoclean-status",
"autoclean-once",
"funderupdate",
"multiwithdraw",
"fundchannel",
"multifundchannel",
"sql",
"listsqlschemas",
"bkpr-listbalances",
"bkpr-listaccountevents",
"bkpr-inspect",
"bkpr-listincome",
"bkpr-dumpincomecsv",
"bkpr-channelsapy"
]
}
Finally, we list only the commands foo
, my-invoice
and my-commands
that we defined today. To do that we modify those commands such that
their category is now clnlive
like this:
/* lightningd/peer_control.c */
static const struct json_command foo_command = {
"foo",
"clnlive",
json_foo,
"foo clnlive #17"
};
/* lightningd/invoice.c */
static const struct json_command my_invoice_command = {
"my-invoice",
"clnlive",
json_my_invoice,
"my-invoice clnlive #17"};
/* lightningd/jsonrpc.c */
static const struct json_command my_commands_command = {
"my-commands",
"clnlive",
json_my_commands,
"foo clnlive #17"
};
In json_my_commands
we want the category
of the commands to be
clnlive
:
static struct command_result *json_my_commands(...)
{
...
for (size_t i = 0; i < tal_count(commands); i++) {
if (streq(commands[i]->category, "clnlive"))
json_add_string(response, NULL, commands[i]->name);
}
...
}
We compile and restart l1
node and we list only the commands foo
,
my-invoice
and my-commands
that we defined today like this:
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"my-invoice",
"my-commands",
"foo"
]
}
We are done!
Terminal session
We ran the following commands in this order:
$ cd lightning/
$ ./configure --disable-rust
$ make clean
$ make -j4
$ rg 'AUTODATA\(json_command'
$ source contrib/startup_regtest.sh
$ start_ln
$ alias l1-cli
$ l1-cli getinfo
$ l1-cli invoice 10000 label-1 description-1
$ l1-cli stop
$ alias l1-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l1-regtest --daemon'
$ l1-start
$ l1-cli foo
$ l1-cli stop
$ l1-start
$ l1-cli foo
$ l1-cli stop
$ l1-start
$ l1-cli foo
$ l1-cli stop
$ l1-start
$ l1-cli foo
$ l1-cli stop
$ l1-start
$ l1-cli my-invoice 10000 description
$ l1-cli my-invoice 10000
$ l1-cli stop
$ l1-start
$ l1-cli my-invoice 10000 description
$ l1-cli my-invoice 10sat description
$ l1-cli my-invoice 1btc description
$ l1-cli stop
$ l1-start
$ l1-cli my-invoice 10000 description
$ l1-cli my-invoice 10000
$ l1-cli stop
$ l1-start
$ l1-cli my-invoice 10000 description
$ l1-cli my-invoice 10000
$ l1-cli stop
$ l1-start
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
$ l1-cli stop
$ l1-start
$ l1-cli my-commands
And below you can read the terminal session (command lines and outputs):
◉ tony@tony:~/clnlive:
$ cd lightning/
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ ./configure --disable-rust
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make clean
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ make -j4
CC: cc -DBINTOPKGLIBEXECDIR="../libexec/c-lightning" -Wall -Wundef -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes -Wold-style-definition -Werror -Wno-maybe-uninitialized -Wshadow=local -std=gnu11 -g -fstack-protector-strong -Og -I ccan -I external/libwally-core/include/ -I external/libwally-core/src/secp256k1/include/ -I external/jsmn/ -I external/libbacktrace/ -I external/gheap/ -I external/build-x86_64-linux-gnu/libbacktrace-build -I external/libsodium/src/libsodium/include -I external/libsodium/src/libsodium/include/sodium -I external/build-x86_64-linux-gnu/libsodium-build/src/libsodium/include -I . -I/usr/local/include -I/usr/include/postgresql -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS -DCOMPAT_V052=1 -DCOMPAT_V060=1 -DCOMPAT_V061=1 -DCOMPAT_V062=1 -DCOMPAT_V070=1 -DCOMPAT_V072=1 -DCOMPAT_V073=1 -DCOMPAT_V080=1 -DCOMPAT_V081=1 -DCOMPAT_V082=1 -DCOMPAT_V090=1 -DCOMPAT_V0100=1 -DCOMPAT_V0121=1 -DBUILD_ELEMENTS=1 -c -o
LD: cc -Og config.vars -Lexternal/build-x86_64-linux-gnu -lwallycore -lsecp256k1 -ljsmn -lbacktrace -lsodium -L/usr/local/include -lm -lsqlite3 -lz -L/usr/lib/x86_64-linux-gnu -lpq -o
...
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ rg 'AUTODATA\(json_command'
lightningd/plugin_control.c
325:AUTODATA(json_command, &plugin_control_command);
lightningd/ping.c
95:AUTODATA(json_command, &ping_command);
lightningd/dual_open_control.c
3544:AUTODATA(json_command, &queryrates_command);
3585:AUTODATA(json_command, &openchannel_init_command);
3586:AUTODATA(json_command, &openchannel_update_command);
3587:AUTODATA(json_command, &openchannel_signed_command);
3588:AUTODATA(json_command, &openchannel_bump_command);
3589:AUTODATA(json_command, &openchannel_abort_command);
lightningd/peer_control.c
2110:AUTODATA(json_command, &listpeers_command);
2163:AUTODATA(json_command, &staticbackup_command);
2223:AUTODATA(json_command, &listpeerchannels_command);
2428:AUTODATA(json_command, &disconnect_command);
2553:AUTODATA(json_command, &getinfo_command);
2659:AUTODATA(json_command, &waitblockheight_command);
2912:AUTODATA(json_command, &setchannel_command);
2976:AUTODATA(json_command, &dev_sign_last_tx);
3017:AUTODATA(json_command, &dev_fail_command);
3076:AUTODATA(json_command, &dev_reenable_commit);
3193:AUTODATA(json_command, &dev_forget_channel_command);
lightningd/signmessage.c
115:AUTODATA(json_command, &json_signmessage_cmd);
243:AUTODATA(json_command, &json_checkmessage_cmd);
lightningd/gossip_control.c
487:AUTODATA(json_command, &setleaserates_command);
536:AUTODATA(json_command, &addgossip_command);
565:AUTODATA(json_command, &dev_set_max_scids_encode_size);
608:AUTODATA(json_command, &dev_compact_gossip_store);
635:AUTODATA(json_command, &dev_gossip_set_time);
lightningd/invoice.c
1225:AUTODATA(json_command, &invoice_command);
1353:AUTODATA(json_command, &listinvoices_command);
1434:AUTODATA(json_command, &delinvoice_command);
1460:AUTODATA(json_command, &delexpiredinvoice_command);
1510:AUTODATA(json_command, &waitanyinvoice_command);
1556:AUTODATA(json_command, &waitinvoice_command);
1592:AUTODATA(json_command, &decodepay_command);
1841:AUTODATA(json_command, &createinvoice_command);
1883:AUTODATA(json_command, &preapproveinvoice_command);
1924:AUTODATA(json_command, &preapprovekeysend_command);
1978:AUTODATA(json_command, &signinvoice_command);
lightningd/closed_channel.c
119:AUTODATA(json_command, &listclosedchannels_command);
lightningd/closing_control.c
906:AUTODATA(json_command, &close_command);
lightningd/offer.c
130:AUTODATA(json_command, &createoffer_command);
192:AUTODATA(json_command, &listoffers_command);
231:AUTODATA(json_command, &disableoffer_command);
499:AUTODATA(json_command, &createinvreq_command);
536:AUTODATA(json_command, &payersign_command);
599:AUTODATA(json_command, &listinvoicerequests_command);
639:AUTODATA(json_command, &disableinvoicerequest_command);
lightningd/log.c
1164:AUTODATA(json_command, &getlog_command);
lightningd/jsonrpc.c
177:AUTODATA(json_command, &help_command);
231:AUTODATA(json_command, &stop_command);
315:AUTODATA(json_command, &dev_command);
1516:AUTODATA(json_command, &check_command);
1542:AUTODATA(json_command, ¬ifications_command);
1568:AUTODATA(json_command, &batching_command);
lightningd/channel_control.c
2070:AUTODATA(json_command, &splice_init_command);
2083:AUTODATA(json_command, &splice_update_command);
2091:AUTODATA(json_command, &splice_signed_command);
2143:AUTODATA(json_command, &dev_feerate_command);
2196:AUTODATA(json_command, &dev_quiesce_command);
lightningd/peer_htlcs.c
2989:AUTODATA(json_command, &dev_ignore_htlcs);
3120:AUTODATA(json_command, &listforwards_command);
3185:AUTODATA(json_command, &delforward_command);
3265:AUTODATA(json_command, &listhtlcs_command);
lightningd/configs.c
338:AUTODATA(json_command, &listconfigs_command);
631:AUTODATA(json_command, &setconfig_command);
lightningd/runes.c
399:AUTODATA(json_command, &showrunes_command);
592:AUTODATA(json_command, &creatrune_command);
600:AUTODATA(json_command, &invokerune_command);
701:AUTODATA(json_command, &blacklistrune_command);
709:AUTODATA(json_command, &destroyrune_command);
848:AUTODATA(json_command, &checkrune_command);
lightningd/wait.c
216:AUTODATA(json_command, &wait_command);
lightningd/connect_control.c
257:AUTODATA(json_command, &connect_command);
825:AUTODATA(json_command, &sendcustommsg_command);
837:AUTODATA(json_command, &dev_sendcustommsg_command);
862:AUTODATA(json_command, &dev_suppress_gossip);
884:AUTODATA(json_command, &dev_report_fds);
lightningd/hsm_control.c
283:AUTODATA(json_command, &makesecret_command);
lightningd/chaintopology.c
783:AUTODATA(json_command, &feerates_command);
810:AUTODATA(json_command, &parse_feerate_command);
lightningd/onion_message.c
245:AUTODATA(json_command, &sendonionmessage_command);
357:AUTODATA(json_command, &blindedpath_command);
lightningd/datastore.c
298:AUTODATA(json_command, &datastore_command);
306:AUTODATA(json_command, &deldatastore_command);
314:AUTODATA(json_command, &listdatastore_command);
lightningd/memdump.c
89:AUTODATA(json_command, &dev_memdump_command);
313:AUTODATA(json_command, &dev_memleak_command);
lightningd/opening_control.c
1552:AUTODATA(json_command, &fundchannel_start_command);
1560:AUTODATA(json_command, &fundchannel_cancel_command);
1569:AUTODATA(json_command, &fundchannel_complete_command);
1579:AUTODATA(json_command, &json_commitchan_command);
lightningd/pay.c
1374:AUTODATA(json_command, &sendonion_command);
1636:AUTODATA(json_command, &sendpay_command);
1683:AUTODATA(json_command, &waitsendpay_command);
1770:AUTODATA(json_command, &listsendpays_command);
1878:AUTODATA(json_command, &delpay_command);
1942:AUTODATA(json_command, &createonion_command);
wallet/reservation.c
147:AUTODATA(json_command, &reserveinputs_command);
230:AUTODATA(json_command, &unreserveinputs_command);
655:AUTODATA(json_command, &fundpsbt_command);
840:AUTODATA(json_command, &utxopsbt_command);
wallet/walletrpc.c
189:AUTODATA(json_command, &newaddr_command);
272:AUTODATA(json_command, &listaddrs_command);
428:AUTODATA(json_command, &listfunds_command);
505:AUTODATA(json_command, &dev_rescan_output_command);
610:AUTODATA(json_command, &listtransactions_command);
824:AUTODATA(json_command, &signpsbt_command);
860:AUTODATA(json_command, &setpsbtversion_command);
1022:AUTODATA(json_command, &sendpsbt_command);
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ 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.08.1)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
error code: -35
error message:
Wallet "default" is already loaded.
[1] 815172
[2] 815218
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.08.1)]
$ alias l1-cli
alias l1-cli='/home/tony/clnlive/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli getinfo
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7",
"alias": "PEEVEDAUTO",
"color": "0370e7",
"num_peers": 0,
"num_pending_channels": 0,
"num_active_channels": 0,
"num_inactive_channels": 0,
"address": [],
"binding": [
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7171
}
],
"version": "v23.08.1",
"blockheight": 1,
"network": "regtest",
"fees_collected_msat": 0,
"lightning-dir": "/tmp/l1-regtest/regtest",
"our_features": {
"init": "08a0000a0269a2",
"node": "88a0000a0269a2",
"channel": "",
"invoice": "02000002024100"
}
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli invoice 10000 label-1 description-1
{
"payment_hash": "4f36f7251095e4e3b2a940ee509d9686f3ebec598b88cacf3a983038980dbe69",
"expires_at": 1701357720,
"bolt11": "lnbcrt100n1pj47mscsp5ll2yqrk8ksdkzdlwpjylclfpun23y2c52zzjfqzptglqxhjvf00spp5fum0wfgsjhjw8v4fgrh9p8vksme7hmze3wyv4ne6nqcr3xqdhe5sdq4v3jhxcmjd9c8g6t0dcknzxqyjw5qcqp29qxpqysgqwl3awdug7qewzjtvhsjda5ysvysnju4nr2ncwmzhd4kn8czysw4ng5ustwq7qwgy074hehexuxudtn9zkjsu3gryke3pvufhea5cgjcql5z6au",
"payment_secret": "ffd4400ec7b41b6137ee0c89fc7d21e4d5122b1450852480415a3e035e4c4bdf",
"created_index": 1,
"warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
[1]- Done test -f "/tmp/l$i-$network/lightningd-$network.pid" || $EATMYDATA "$LIGHTNINGD" "--network=$network" "--lightning-dir=/tmp/l$i-$network" "--bitcoin-datadir=$PATH_TO_BITCOIN" "--database-upgrade=true"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ alias l1-start='/home/tony/clnlive/lightning/lightningd/lightningd --lightning-dir=/tmp/l1-regtest --daemon'
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7",
"alias": "PEEVEDAUTO",
"color": "0370e7",
"num_peers": 0,
"num_pending_channels": 0,
"num_active_channels": 0,
"num_inactive_channels": 0,
"address": [],
"binding": [
{
"type": "ipv4",
"address": "127.0.0.1",
"port": 7171
}
],
"version": "v23.08.1-modded",
"blockheight": 1,
"network": "regtest",
"fees_collected_msat": 0,
"lightning-dir": "/tmp/l1-regtest/regtest",
"our_features": {
"init": "08a0000a0269a2",
"node": "88a0000a0269a2",
"channel": "",
"invoice": "02000002024100"
}
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"bar": "baz"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"foo": {
"bar": "baz"
}
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli foo
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000
{
"code": -32602,
"message": "missing required parameter: description"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{
"amount_msat": 10000
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10sat description
{
"amount_msat": 10000
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 1btc description
{
"amount_msat": 100000000000
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{
"amount_msat": 10000,
"description": "description"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000
Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.Lost connection to the RPC socket.lightning-cli: reading response: socket closed
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
lightning-cli: Connecting to 'lightning-rpc': Connection refused
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000 description
{
"amount_msat": 10000,
"description": "description"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-invoice 10000
{
"amount_msat": 10000
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
Cannot add duplicate command foo
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
lightning-cli: Connecting to 'lightning-rpc': Connection refused
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"id": "0370e7cead9fbb0845ef7dff5b7b4a2c4eab8b722456ed0bca7747ff680c3de0f7"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"command": "feerates"
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"feerates"
]
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"feerates",
"parsefeerate",
"splice_init",
"splice_update",
"splice_signed",
"close",
"openchannel_init",
"openchannel_update",
"openchannel_signed",
"openchannel_bump",
"openchannel_abort",
"listclosedchannels",
"connect",
"sendcustommsg",
"dev-sendcustommsg",
"sendonionmessage",
"blindedpath",
"setleaserates",
"addgossip",
"makesecret",
"invoice",
"my-invoice",
"listinvoices",
"delinvoice",
"delexpiredinvoice",
"waitanyinvoice",
"waitinvoice",
"decodepay",
"createinvoice",
"preapproveinvoice",
"preapprovekeysend",
"signinvoice",
"help",
"my-commands",
"stop",
"check",
"notifications",
"batching",
"getlog",
"fundchannel_start",
"fundchannel_cancel",
"fundchannel_complete",
"recoverchannel",
"sendonion",
"sendpay",
"waitsendpay",
"listsendpays",
"delpay",
"createonion",
"listpeers",
"staticbackup",
"listpeerchannels",
"disconnect",
"getinfo",
"foo",
"waitblockheight",
"setchannel",
"listforwards",
"delforward",
"listhtlcs",
"plugin",
"showrunes",
"createrune",
"invokerune",
"blacklistrune",
"destroyrune",
"checkrune",
"wait",
"listconfigs",
"setconfig",
"datastore",
"deldatastore",
"listdatastore",
"ping",
"createoffer",
"listoffers",
"disableoffer",
"createinvoicerequest",
"payersign",
"listinvoicerequests",
"disableinvoicerequest",
"signmessage",
"checkmessage",
"newaddr",
"dev-listaddrs",
"listfunds",
"dev-rescan-outputs",
"listtransactions",
"signpsbt",
"setpsbtversion",
"sendpsbt",
"reserveinputs",
"unreserveinputs",
"fundpsbt",
"utxopsbt",
"emergencyrecover",
"restorefrompeer",
"keysend",
"getrawblockbyheight",
"getchaininfo",
"estimatefees",
"sendrawtransaction",
"getutxout",
"commando",
"commando-rune",
"commando-listrunes",
"commando-blacklist",
"getroute",
"listchannels",
"listnodes",
"listincoming",
"offer",
"invoicerequest",
"decode",
"paystatus",
"listpays",
"pay",
"renepaystatus",
"renepay",
"autocleaninvoice",
"autoclean-status",
"autoclean-once",
"funderupdate",
"multiwithdraw",
"fundchannel",
"multifundchannel",
"sql",
"listsqlschemas",
"txprepare",
"txdiscard",
"txsend",
"withdraw",
"upgradewallet",
"bkpr-listbalances",
"bkpr-listaccountevents",
"bkpr-inspect",
"bkpr-listincome",
"bkpr-dumpincomecsv",
"bkpr-channelsapy"
]
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"plugin",
"emergencyrecover",
"restorefrompeer",
"keysend",
"renepaystatus",
"renepay",
"getrawblockbyheight",
"getchaininfo",
"estimatefees",
"sendrawtransaction",
"getutxout",
"commando",
"commando-rune",
"commando-listrunes",
"commando-blacklist",
"getroute",
"listchannels",
"listnodes",
"listincoming",
"offer",
"invoicerequest",
"decode",
"paystatus",
"listpays",
"pay",
"txprepare",
"txdiscard",
"txsend",
"withdraw",
"upgradewallet",
"autocleaninvoice",
"autoclean-status",
"autoclean-once",
"funderupdate",
"multiwithdraw",
"fundchannel",
"multifundchannel",
"sql",
"listsqlschemas",
"bkpr-listbalances",
"bkpr-listaccountevents",
"bkpr-inspect",
"bkpr-listincome",
"bkpr-dumpincomecsv",
"bkpr-channelsapy"
]
}
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli stop
"Shutdown complete"
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-start
◉ tony@tony:~/clnlive/lightning:[git»(HEAD detached at v23.08.1)]
$ l1-cli my-commands
{
"commands": [
"my-invoice",
"my-commands",
"foo"
]
}
Core Lightning source code
getinfo
json_getinfo in lightning:lightningd/peer_control.c
static struct command_result *json_getinfo(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
struct peer *peer;
struct channel *channel;
unsigned int pending_channels = 0, active_channels = 0,
inactive_channels = 0, num_peers = 0;
size_t count_announceable;
struct peer_node_id_map_iter it;
if (!param(cmd, buffer, params, NULL))
return command_param_failed();
response = json_stream_success(cmd);
json_add_node_id(response, "id", &cmd->ld->id);
json_add_string(response, "alias", (const char *)cmd->ld->alias);
json_add_hex_talarr(response, "color", cmd->ld->rgb);
/* Add some peer and channel stats */
for (peer = peer_node_id_map_first(cmd->ld->peers, &it);
peer;
peer = peer_node_id_map_next(cmd->ld->peers, &it)) {
num_peers++;
list_for_each(&peer->channels, channel, list) {
if (channel->state == CHANNELD_AWAITING_LOCKIN
|| channel->state == DUALOPEND_AWAITING_LOCKIN
|| channel->state == DUALOPEND_OPEN_INIT) {
pending_channels++;
} else if (channel_active(channel)) {
active_channels++;
} else {
inactive_channels++;
}
}
}
json_add_num(response, "num_peers", num_peers);
json_add_num(response, "num_pending_channels", pending_channels);
json_add_num(response, "num_active_channels", active_channels);
json_add_num(response, "num_inactive_channels", inactive_channels);
/* Add network info */
json_array_start(response, "address");
if (cmd->ld->listen) {
/* These are the addresses we're announcing */
count_announceable = tal_count(cmd->ld->announceable);
for (size_t i = 0; i < count_announceable; i++)
json_add_address(response, NULL, cmd->ld->announceable+i);
/* Add discovered IPs if we announce them.
* Also see `create_node_announcement` in `gossip_generation.c`. */
if ((cmd->ld->config.ip_discovery == OPT_AUTOBOOL_AUTO && count_announceable == 0) ||
cmd->ld->config.ip_discovery == OPT_AUTOBOOL_TRUE) {
if (cmd->ld->discovered_ip_v4 != NULL &&
!wireaddr_arr_contains(
cmd->ld->announceable,
cmd->ld->discovered_ip_v4))
json_add_address(response, NULL,
cmd->ld->discovered_ip_v4);
if (cmd->ld->discovered_ip_v6 != NULL &&
!wireaddr_arr_contains(
cmd->ld->announceable,
cmd->ld->discovered_ip_v6))
json_add_address(response, NULL,
cmd->ld->discovered_ip_v6);
}
json_array_end(response);
/* This is what we're actually bound to. */
json_array_start(response, "binding");
for (size_t i = 0; i < tal_count(cmd->ld->binding); i++)
json_add_address_internal(response, NULL,
cmd->ld->binding+i);
}
json_array_end(response);
json_add_string(response, "version", version());
/* If we're still syncing, put the height we're up to here, so
* they can see progress! Otherwise use the height gossipd knows
* about, so tests work properly. */
if (!topology_synced(cmd->ld->topology)) {
json_add_num(response, "blockheight",
get_block_height(cmd->ld->topology));
} else {
json_add_num(response, "blockheight",
cmd->ld->gossip_blockheight);
}
json_add_string(response, "network", chainparams->network_name);
json_add_amount_msat(response,
"fees_collected_msat",
wallet_total_forward_fees(cmd->ld->wallet));
json_add_string(response, "lightning-dir", cmd->ld->config_netdir);
if (!cmd->ld->topology->bitcoind->synced)
json_add_string(response, "warning_bitcoind_sync",
"Bitcoind is not up-to-date with network.");
else if (!topology_synced(cmd->ld->topology))
json_add_string(response, "warning_lightningd_sync",
"Still loading latest blocks from bitcoind.");
u8 **bits = cmd->ld->our_features->bits;
json_object_start(response, "our_features");
json_add_hex_talarr(response, "init",
featurebits_or(cmd,
bits[INIT_FEATURE],
bits[GLOBAL_INIT_FEATURE]));
json_add_hex_talarr(response, "node", bits[NODE_ANNOUNCE_FEATURE]);
json_add_hex_talarr(response, "channel", bits[CHANNEL_FEATURE]);
json_add_hex_talarr(response, "invoice", bits[BOLT11_FEATURE]);
json_object_end(response);
return command_success(cmd, response);
}
static const struct json_command getinfo_command = {
"getinfo",
"utility",
json_getinfo,
"Show information about this node"
};
AUTODATA(json_command, &getinfo_command);
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;
#if DEVELOPER
const jsmntok_t *routes;
#endif
info = tal(cmd, struct invoice_info);
info->cmd = cmd;
if (!param(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),
#if DEVELOPER
p_opt("dev-routes", param_array, &routes),
#endif
NULL))
return command_param_failed();
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 (fallbacks) {
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;
}
}
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]);
#if DEVELOPER
info->b11->routes = unpack_routes(info->b11, buffer, routes);
#else
info->b11->routes = NULL;
#endif
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);
}
static const struct json_command invoice_command = {
"invoice",
"payment",
json_invoice,
"Create an invoice for {msatoshi} with {label} "
"and {description} with optional {expiry} seconds "
"(default 1 week), optional {fallbacks} address list"
"(default empty list) and optional {preimage} "
"(default autogenerated)"};
AUTODATA(json_command, &invoice_command);
help
json_help in lightning:lightningd/jsonrpc.c
static struct command_result *json_help(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
const char *cmdname;
struct json_command **commands;
const struct json_command *one_cmd;
if (!param(cmd, buffer, params,
p_opt("command", param_string, &cmdname),
NULL))
return command_param_failed();
commands = cmd->ld->jsonrpc->commands;
if (cmdname) {
one_cmd = find_command(commands, cmdname);
if (!one_cmd)
return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
"Unknown command %s",
cmdname);
if (!cmd->ld->deprecated_apis && one_cmd->deprecated)
return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
"Deprecated command %s",
cmdname);
} else
one_cmd = NULL;
asort(commands, tal_count(commands), compare_commands_name, NULL);
response = json_stream_success(cmd);
json_array_start(response, "help");
for (size_t i = 0; i < tal_count(commands); i++) {
if (!one_cmd || one_cmd == commands[i])
json_add_help_command(cmd, response, commands[i]);
}
json_array_end(response);
/* Tell cli this is simple enough to be formatted flat for humans */
json_add_string(response, "format-hint", "simple");
return command_success(cmd, response);
}
command, json_command, json_stream and jsonrpc structs
command in lightning:lightningd/jsonrpc.h
/* Context for a command (from JSON, but might outlive the connection!). */
/* FIXME: move definition into jsonrpc.c */
struct command {
/* Off list jcon->commands */
struct list_node list;
/* The global state */
struct lightningd *ld;
/* The 'id' which we need to include in the response. */
const char *id;
/* If 'id' needs to be quoted (i.e. it's a string) */
bool id_is_string;
/* What command we're running (for logging) */
const struct json_command *json_cmd;
/* The connection, or NULL if it closed. */
struct json_connection *jcon;
/* Does this want notifications? */
bool send_notifications;
/* Have we been marked by command_still_pending? For debugging... */
bool pending;
/* Tell param() how to process the command */
enum command_mode mode;
/* Have we started a json stream already? For debugging. */
struct json_stream *json_stream;
/* Optional output field filter. */
struct json_filter *filter;
};
json_command in lightning:lightningd/jsonrpc.h
struct json_command {
const char *name;
const char *category;
struct command_result *(*dispatch)(struct command *,
const char *buffer,
const jsmntok_t *obj,
const jsmntok_t *params);
const char *description;
bool deprecated;
const char *verbose;
};
json_stream in lightning:common/json_stream.h
struct json_stream {
struct json_out *jout;
/* Who is writing to this buffer now; NULL if nobody is. */
struct command *writer;
/* Who is io_writing from this buffer now: NULL if nobody is. */
struct io_conn *reader;
struct io_plan *(*reader_cb)(struct io_conn *conn,
struct json_stream *js,
void *arg);
void *reader_arg;
size_t len_read;
/* If non-NULL, reflects the current filter position */
struct json_filter *filter;
/* Where to log I/O */
struct logger *log;
};
jsonrpc in lightning:lightningd/jsonrpc.c
/**
* `jsonrpc` encapsulates the entire state of the JSON-RPC interface,
* including a list of methods that the interface supports (can be
* appended dynamically, e.g., for plugins, and logs. It also serves
* as a convenient `tal`-parent for all JSON-RPC related allocations.
*/
struct jsonrpc {
struct io_listener *rpc_listener;
struct json_command **commands;
/* Map from json command names to usage strings: we don't put this inside
* struct json_command as it's good practice to have those const. */
STRMAP(const char *) usagemap;
};
Command parameters
param in lightning:common/json_param.c
bool param(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...)
{
struct param *params = tal_arr(tmpctx, struct param, 0);
const char *name;
va_list ap;
bool allow_extra = false;
va_start(ap, tokens);
while ((name = va_arg(ap, const char *)) != NULL) {
enum param_style style = va_arg(ap, enum param_style);
param_cbx cbx = va_arg(ap, param_cbx);
void *arg = va_arg(ap, void *);
if (streq(name, "")) {
allow_extra = true;
continue;
}
param_add(¶ms, name, style, cbx, arg);
}
va_end(ap);
if (command_usage_only(cmd)) {
check_params(params);
command_set_usage(cmd, param_usage(cmd, params));
return false;
}
/* Always return false if we're simply checking command parameters;
* normally this returns true if all parameters are valid. */
return param_arr(cmd, buffer, tokens, params, allow_extra) == NULL
&& !command_check_only(cmd);
}
p_req in lightning:common/json_param.h
/*
* Add a required parameter.
*/
#define p_req(name, cbx, arg) \
name"", \
PARAM_REQUIRED, \
(param_cbx)(cbx), \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \
(const char *)NULL, \
(const jsmntok_t *)NULL, \
(arg)) == (struct command_result *)NULL)
p_opt in lightning:common/json_param.h
/*
* Add an optional parameter. *arg is set to NULL if it isn't found.
*/
#define p_opt(name, cbx, arg) \
name"", \
PARAM_OPTIONAL, \
(param_cbx)(cbx), \
({ *arg = NULL; \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \
(const char *)NULL, \
(const jsmntok_t *)NULL, \
(arg)) == (struct command_result *)NULL); })
param_positive_msat_or_any in lightning:lightningd/invoice.c
static struct command_result *param_positive_msat_or_any(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct amount_msat **msat)
{
if (json_tok_streq(buffer, tok, "any")) {
*msat = NULL;
return NULL;
}
*msat = tal(cmd, struct amount_msat);
if (parse_amount_msat(*msat, buffer + tok->start, tok->end - tok->start)
&& !amount_msat_eq(**msat, AMOUNT_MSAT(0)))
return NULL;
return command_fail_badparam(cmd, name, buffer, tok,
"should be positive msat or 'any'");
}
param_msat in lightning:common/json_param.c
struct command_result *param_msat(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct amount_msat **msat)
{
*msat = tal(cmd, struct amount_msat);
if (parse_amount_msat(*msat, buffer + tok->start, tok->end - tok->start))
return NULL;
return command_fail_badparam(cmd, name, buffer, tok,
"should be a millisatoshi amount");
}
param_escaped_string in lightning:common/json_param.c
struct command_result *param_escaped_string(struct command *cmd,
const char *name,
const char * buffer,
const jsmntok_t *tok,
const char **str)
{
if (tok->type == JSMN_STRING) {
struct json_escape *esc;
/* jsmn always gives us ~ well-formed strings. */
esc = json_escape_string_(cmd, buffer + tok->start,
tok->end - tok->start);
*str = json_escape_unescape(cmd, esc);
if (*str)
return NULL;
}
return command_fail_badparam(cmd, name, buffer, tok,
"should be a string (without \\u)");
}
command_param_failed in lightning:lightningd/jsonrpc.c
struct command_result *command_param_failed(void)
{
return ¶m_failed;
}
json_stream_success and command_success
json_stream_success in lightning:lightningd/jsonrpc.c
struct json_stream *json_stream_success(struct command *cmd)
{
struct json_stream *r = json_start(cmd);
json_object_start(r, "result");
/* We have results? OK, start filtering */
if (cmd->filter)
json_stream_attach_filter(r, cmd->filter);
return r;
}
command_success in lightning:lightningd/jsonrpc.c
struct command_result *command_success(struct command *cmd,
struct json_stream *result)
{
assert(cmd);
assert(cmd->json_stream == result);
/* Filter will get upset if we close "result" object it didn't
* see! */
if (cmd->filter) {
const char *err = json_stream_detach_filter(tmpctx, result);
if (err)
json_add_string(result, "warning_parameter_filter",
err);
}
json_object_end(result);
json_object_end(result);
return command_raw_complete(cmd, result);
}
common/json_stream.c
json_add_node_id in lightning:common/json_stream.c
void json_add_node_id(struct json_stream *response,
const char *fieldname,
const struct node_id *id)
{
json_add_hex(response, fieldname, id->k, sizeof(id->k));
}
json_add_amount_msat in lightning:common/json_stream.c
void json_add_amount_msat(struct json_stream *result,
const char *msatfieldname,
struct amount_msat msat)
{
assert(strends(msatfieldname, "_msat"));
json_add_u64(result, msatfieldname, msat.millisatoshis); /* Raw: low-level helper */
}
json_add_string in lightning:common/json_stream.c
void json_add_string(struct json_stream *js,
const char *fieldname,
const char *str TAKES)
{
if (json_filter_ok(js->filter, fieldname))
json_out_addstr(js->jout, fieldname, str);
if (taken(str))
tal_free(str);
}
json_object_start in lightning:common/json_stream.c
void json_object_start(struct json_stream *js, const char *fieldname)
{
if (json_filter_down(&js->filter, fieldname))
json_out_start(js->jout, fieldname, '{');
}
json_object_end in lightning:common/json_stream.c
void json_object_end(struct json_stream *js)
{
if (json_filter_up(&js->filter))
json_out_end(js->jout, '}');
}
json_array_start in lightning:common/json_stream.c
void json_array_start(struct json_stream *js, const char *fieldname)
{
if (json_filter_down(&js->filter, fieldname))
json_out_start(js->jout, fieldname, '[');
}
json_array_end in lightning:common/json_stream.c
void json_array_end(struct json_stream *js)
{
if (json_filter_up(&js->filter))
json_out_end(js->jout, ']');
}
AUTODATA
AUTODATA in lightning:common/autodata.h
/* This uses GCC's constructor attribute */
#define AUTODATA(name, ptr) \
static __attribute__((constructor)) NEEDED \
void CPPMAGIC_GLUE2(register_one_##name,__COUNTER__)(void) { \
register_autotype_##name(ptr); \
}
tal_count and streq in ccan
tal_count in ccan:ccan/tal/tal.h
/**
* tal_count - get the count of objects in a tal object.
* @ptr: The tal allocated object (or NULL)
*
* Returns 0 if @ptr is NULL. Note that if the allocation was done as a
* different type to @ptr, the result may not match the @count argument
* (or implied 1) of that allocation!
*/
#define tal_count(p) (tal_bytelen(p) / sizeof(*p))
/**
* streq - Are two strings equal?
* @a: first string
* @b: first string
*
* This macro is arguably more readable than "!strcmp(a, b)".
*
* Example:
* if (streq(somestring, ""))
* printf("String is empty!\n");
*/
#define streq(a,b) (strcmp((a),(b)) == 0)
Source code
json_foo
/* clnlive #17 */
static struct command_result *json_foo(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
if (!param(cmd, buffer, params, NULL))
return command_param_failed();
response = json_stream_success(cmd);
json_add_node_id(response, "id", &cmd->ld->id);
/* json_object_start(response, "foo"); */
/* json_add_string(response, "bar", "baz"); */
/* json_object_end(response); */
return command_success(cmd, response);
}
static const struct json_command foo_command = {
"foo",
"clnlive",
json_foo,
"foo clnlive #17"
};
AUTODATA(json_command, &foo_command);
json_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);
json_my_commands
/* clnlive #17 */
static struct command_result *json_my_commands(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
struct json_command **commands;
if (!param(cmd, buffer, params, NULL))
return command_param_failed();
commands = cmd->ld->jsonrpc->commands;
response = json_stream_success(cmd);
json_array_start(response, "commands");
for (size_t i = 0; i < tal_count(commands); i++) {
if (streq(commands[i]->category, "clnlive"))
json_add_string(response, NULL, commands[i]->name);
}
json_array_end(response);
return command_success(cmd, response);
}
static const struct json_command my_commands_command = {
"my-commands",
"clnlive",
json_my_commands,
"foo clnlive #17"
};
AUTODATA(json_command, &my_commands_command);
clnlive-17.patch
From f96e4d67e9c6582023595a137687f78b4fdaf13a Mon Sep 17 00:00:00 2001
From: Tony Aldon <tony@tonyaldon.com>
Date: Fri, 24 Nov 2023 06:03:01 +0100
Subject: [PATCH] clnlive #17
---
lightningd/invoice.c | 31 +++++++++++++++++++++++++++++++
lightningd/jsonrpc.c | 35 +++++++++++++++++++++++++++++++++++
lightningd/peer_control.c | 29 +++++++++++++++++++++++++++++
3 files changed, 95 insertions(+)
diff --git a/lightningd/invoice.c b/lightningd/invoice.c
index cc7580a4f..f882ece98 100644
--- a/lightningd/invoice.c
+++ b/lightningd/invoice.c
@@ -1224,6 +1224,37 @@ static const struct json_command invoice_command = {
"(default autogenerated)"};
AUTODATA(json_command, &invoice_command);
+/* 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);
+
static void json_add_invoices(struct json_stream *response,
struct wallet *wallet,
const struct json_escape *label,
diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c
index d3aa23b12..f1f8ae7de 100644
--- a/lightningd/jsonrpc.c
+++ b/lightningd/jsonrpc.c
@@ -176,6 +176,41 @@ static const struct json_command help_command = {
};
AUTODATA(json_command, &help_command);
+/* clnlive #17 */
+
+static struct command_result *json_my_commands(struct command *cmd,
+ const char *buffer,
+ const jsmntok_t *obj UNNEEDED,
+ const jsmntok_t *params)
+{
+ struct json_stream *response;
+ struct json_command **commands;
+
+ if (!param(cmd, buffer, params, NULL))
+ return command_param_failed();
+
+ commands = cmd->ld->jsonrpc->commands;
+
+ response = json_stream_success(cmd);
+ json_array_start(response, "commands");
+ for (size_t i = 0; i < tal_count(commands); i++) {
+ if (streq(commands[i]->category, "clnlive"))
+ json_add_string(response, NULL, commands[i]->name);
+ }
+ json_array_end(response);
+
+
+ return command_success(cmd, response);
+}
+
+static const struct json_command my_commands_command = {
+ "my-commands",
+ "clnlive",
+ json_my_commands,
+ "foo clnlive #17"
+};
+AUTODATA(json_command, &my_commands_command);
+
static struct command_result *json_stop(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c
index 9455d3e0a..b177e05a4 100644
--- a/lightningd/peer_control.c
+++ b/lightningd/peer_control.c
@@ -2552,6 +2552,35 @@ static const struct json_command getinfo_command = {
};
AUTODATA(json_command, &getinfo_command);
+/* clnlive #17 */
+
+static struct command_result *json_foo(struct command *cmd,
+ const char *buffer,
+ const jsmntok_t *obj UNNEEDED,
+ const jsmntok_t *params)
+{
+ struct json_stream *response;
+
+ if (!param(cmd, buffer, params, NULL))
+ return command_param_failed();
+
+ response = json_stream_success(cmd);
+ json_add_node_id(response, "id", &cmd->ld->id);
+ /* json_object_start(response, "foo"); */
+ /* json_add_string(response, "bar", "baz"); */
+ /* json_object_end(response); */
+
+ return command_success(cmd, response);
+}
+
+static const struct json_command foo_command = {
+ "foo",
+ "clnlive",
+ json_foo,
+ "foo clnlive #17"
+};
+AUTODATA(json_command, &foo_command);
+
/* Wait for at least a specific blockheight, then return, or time out. */
struct waitblockheight_waiter {
/* struct lightningd::waitblockheight_commands. */
--
2.34.1