Skip to content
Open
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d4e5a30
bookkeeper: fix printing of bad JSON results.
rustyrussell Nov 10, 2025
8b7f351
plugins/sql: remove size limit.
rustyrussell Nov 10, 2025
0c7c415
plugins/sql: print times taken to do list comand, populate table, and…
rustyrussell Nov 10, 2025
76687dd
bookkeeper: no longer read listchannelmoves 1000 entries at a time.
rustyrussell Nov 10, 2025
0cd1624
Revert "bookkeeper: don't flood logs if we have many channelmoves all…
rustyrussell Nov 10, 2025
76f5ae8
pytest: latency and speed test on large coinmoves.
rustyrussell Nov 10, 2025
52b479a
lightningd: don't copy hooks array into hook request, simply don't sh…
rustyrussell Nov 10, 2025
19a05e7
lightningd: don't loop through all commands every time one finishes.
rustyrussell Nov 10, 2025
10d2745
common: remove tracing exponential behaviour from large numbers of re…
rustyrussell Nov 10, 2025
fca97f7
common: avoid allocations for small numbers of traces.
rustyrussell Nov 10, 2025
3f13e71
pytest: increase test_generate_coinmoves to 2M entries.
rustyrussell Nov 10, 2025
b76ad39
lightningd: handle large numbers of command outputs gracefully.
rustyrussell Nov 10, 2025
15ea6c1
JSONRPC: use a bigger default buffer.
rustyrussell Nov 10, 2025
f9cadf5
lightningd: support "filters" in plugins manifest to restrict when ho…
rustyrussell Nov 10, 2025
f80602b
lightningd: add support for filters on "rpc_command" hook.
rustyrussell Nov 10, 2025
9cfe0d0
libplugin: allow plugins to register optional filters for each hook t…
rustyrussell Nov 10, 2025
4c90473
xpay: use filtering on rpc_command so we only get called on "pay".
rustyrussell Nov 10, 2025
c0a5f62
pyln-client: support hook filters.
rustyrussell Nov 10, 2025
ed4349b
lightningd: allow filtering on custommsg hook too.
rustyrussell Nov 10, 2025
c5044ef
commando, chanbackup: use custommsg hooks.
rustyrussell Nov 10, 2025
800ff96
common: increase jsonrpc_io buffer size temporarily to aggrevate perf…
rustyrussell Nov 10, 2025
2d01a34
common: optimize json parsing.
rustyrussell Nov 10, 2025
817367c
ccan: update to get io_loop fairness.
rustyrussell Nov 10, 2025
13f93e6
lightningd: don't process more than 100 commands from a JSONRPC at once.
rustyrussell Nov 10, 2025
fd989ae
lightningd: don't process more than 100 commands from a plugin at once.
rustyrussell Nov 10, 2025
0dc876c
plugins/sql: use modern data style, not globals.
rustyrussell Nov 10, 2025
cfb27fb
sql: if we use `dev-sqlfilename`, don't bother syncing it to disk.
rustyrussell Nov 10, 2025
54cab8e
sql: use wait RPC so we don't have to check listchannelmoves/listchai…
rustyrussell Nov 10, 2025
46c551e
lightningd: optimize find_cmd.
rustyrussell Nov 10, 2025
bee028f
bookkeeper: restore limit on asking for all channelmoves at once.
rustyrussell Nov 10, 2025
db1f508
sql: limit how many chainmoves/channelmoves entries we ask for at once.
rustyrussell Nov 10, 2025
025e7d9
pytest: increase test_generate_coinmoves to 5M entries.
rustyrussell Nov 10, 2025
41f254d
pytest: test for 1M JSONRPC calls which don't need transactions.
rustyrussell Nov 10, 2025
aec622c
db: don't start transactions unless we really need to.
rustyrussell Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 88 additions & 91 deletions lightningd/jsonrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ struct json_connection {
struct list_head jsouts;
};

/* We don't put usage inside struct json_command as it's good practice
* to have those const. */
struct cmd_and_usage {
const struct json_command *command;
const char *usage;
};

/**
* `jsonrpc` encapsulates the entire state of the JSON-RPC interface,
* including a list of methods that the interface supports (can be
Expand All @@ -108,11 +115,9 @@ struct json_connection {
*/
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;
/* Can't be const: we set ->usage later */
STRMAP(struct cmd_and_usage *) cmdmap;
};

/* The command itself usually owns the stream, because jcon may get closed.
Expand Down Expand Up @@ -400,85 +405,74 @@ static const struct json_command dev_command = {
};
AUTODATA(json_command, &dev_command);

static size_t num_cmdlist;

static struct json_command **get_cmdlist(void)
static struct json_command **get_cmdlist(size_t *num_cmdlist)
{
static struct json_command **cmdlist;
if (!cmdlist)
cmdlist = autodata_get(json_command, &num_cmdlist);
cmdlist = autodata_get(json_command, num_cmdlist);

return cmdlist;
}

static void json_add_help_command(struct command *cmd,
struct json_stream *response,
struct json_command *json_command)
struct json_help_info {
struct command *cmd;
struct json_stream *response;
};

/* Used as a strmap_iterate function: returns true to continue */
static bool json_add_help_command(const char *cmdname,
struct cmd_and_usage *cmd,
struct json_help_info *hinfo)
{
char *usage;

/* If they disallow deprecated APIs, don't even list them */
if (!command_deprecated_out_ok(cmd, NULL,
json_command->depr_start,
json_command->depr_end)) {
return;
}

usage = tal_fmt(cmd, "%s%s %s",
json_command->name,
json_command->depr_start ? " (DEPRECATED!)" : "",
strmap_get(&cmd->ld->jsonrpc->usagemap,
json_command->name));
json_object_start(response, NULL);
json_add_string(response, "command", usage);
json_object_end(response);
}

static const struct json_command *find_command(struct json_command **commands,
const char *cmdname)
{
for (size_t i = 0; i < tal_count(commands); i++) {
if (streq(cmdname, commands[i]->name))
return commands[i];
if (!command_deprecated_out_ok(hinfo->cmd, NULL,
cmd->command->depr_start,
cmd->command->depr_end)) {
return true;
}
return NULL;
}

static int compare_commands_name(struct json_command *const *a,
struct json_command *const *b, void *unused)
{
return strcmp((*a)->name, (*b)->name);
assert(cmd->command);
assert(!streq(cmd->command->name, "xxxxX"));
assert(!streq(cmd->usage, "xxxxX"));
Comment on lines +437 to +438
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do these constants come from? Are they used somewhere for testing, otherwise I'd remove these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! I was debugging a crash, trying to find out if cmd, cmd->command->name or cmd->usage was null. Removed.

usage = tal_fmt(tmpctx, "%s%s %s",
cmd->command->name,
cmd->command->depr_start ? " (DEPRECATED!)" : "",
cmd->usage);
json_object_start(hinfo->response, NULL);
json_add_string(hinfo->response, "command", usage);
json_object_end(hinfo->response);
return true;
}

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;
struct cmd_and_usage *one_cmd;
struct json_help_info hinfo;

if (!param_check(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);
one_cmd = strmap_get(&cmd->ld->jsonrpc->cmdmap, cmdname);
if (!one_cmd)
return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
"Unknown command %s",
cmdname);
if (!command_deprecated_in_ok(cmd, NULL,
one_cmd->depr_start,
one_cmd->depr_end))
one_cmd->command->depr_start,
one_cmd->command->depr_end))
return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
"Deprecated command %s",
cmdname);
if (!cmd->ld->developer && one_cmd->dev_only)
if (!cmd->ld->developer && one_cmd->command->dev_only)
return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
"Developer-only command %s",
cmdname);
Expand All @@ -488,31 +482,32 @@ static struct command_result *json_help(struct command *cmd,
if (command_check_only(cmd))
return command_check_done(cmd);

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]);
hinfo.cmd = cmd;
hinfo.response = json_stream_success(cmd);
json_array_start(hinfo.response, "help");
if (one_cmd)
json_add_help_command(cmdname, one_cmd, &hinfo);
else {
strmap_iterate(&cmd->ld->jsonrpc->cmdmap,
json_add_help_command, &hinfo);
}
json_array_end(response);
json_array_end(hinfo.response);

/* Tell cli this is simple enough to be formatted flat for humans */
json_add_string(response, "format-hint", "simple");
json_add_string(hinfo.response, "format-hint", "simple");

return command_success(cmd, response);
return command_success(cmd, hinfo.response);
}

static const struct json_command *find_cmd(const struct jsonrpc *rpc,
const char *buffer,
const jsmntok_t *tok)
{
struct json_command **commands = rpc->commands;
const struct cmd_and_usage *cmd;

for (size_t i = 0; i < tal_count(commands); i++)
if (json_tok_streq(buffer, tok, commands[i]->name))
return commands[i];
cmd = strmap_getn(&rpc->cmdmap, buffer + tok->start, tok->end - tok->start);
if (cmd)
return cmd->command;
return NULL;
}

Expand Down Expand Up @@ -1286,27 +1281,26 @@ static struct io_plan *incoming_jcon_connected(struct io_conn *conn,

static void destroy_json_command(struct json_command *command, struct jsonrpc *rpc)
{
strmap_del(&rpc->usagemap, command->name, NULL);
for (size_t i = 0; i < tal_count(rpc->commands); i++) {
if (rpc->commands[i] == command) {
tal_arr_remove(&rpc->commands, i);
return;
}
}
abort();
struct cmd_and_usage *cmd;

if (!strmap_del(&rpc->cmdmap, command->name, &cmd))
abort();
tal_free(cmd);
}

static bool command_add(struct jsonrpc *rpc, struct json_command *command)
static struct cmd_and_usage *command_add(struct jsonrpc *rpc, struct json_command *command)
{
size_t count = tal_count(rpc->commands);
struct cmd_and_usage *cmd;

/* Check that we don't clobber a method */
for (size_t i = 0; i < count; i++)
if (streq(rpc->commands[i]->name, command->name))
return false;
if (strmap_get(&rpc->cmdmap, command->name))
return NULL;

tal_arr_expand(&rpc->commands, command);
return true;
cmd = tal(rpc, struct cmd_and_usage);
cmd->command = command;
cmd->usage = NULL;
strmap_add(&rpc->cmdmap, command->name, cmd);
return cmd;
}

/* Built-in commands get called to construct usage string via param() */
Expand All @@ -1323,22 +1317,23 @@ static void setup_command_usage(struct lightningd *ld,
dummy->json_cmd = command;
res = command->dispatch(dummy, NULL, NULL, NULL);
assert(res == &param_failed);
assert(strmap_get(&ld->jsonrpc->usagemap, command->name));
assert(strmap_get(&ld->jsonrpc->cmdmap, command->name)->usage);
}

bool jsonrpc_command_add(struct jsonrpc *rpc, struct json_command *command,
const char *usage TAKES)
{
const char *unescaped;
struct cmd_and_usage *cmd;

if (!command_add(rpc, command))
cmd = command_add(rpc, command);
if (!cmd)
return false;

unescaped = json_escape_unescape_len(command, usage, strlen(usage));
if (!unescaped)
cmd->usage = json_escape_unescape_len(cmd, usage, strlen(usage));
if (!cmd->usage) {
tal_free(cmd);
return false;

strmap_add(&rpc->usagemap, command->name, unescaped);
}
tal_add_destructor2(command, destroy_json_command, rpc);
return true;
}
Expand All @@ -1355,22 +1350,22 @@ static bool jsonrpc_command_add_perm(struct lightningd *ld,

static void destroy_jsonrpc(struct jsonrpc *jsonrpc)
{
strmap_clear(&jsonrpc->usagemap);
strmap_clear(&jsonrpc->cmdmap);
}

static void memleak_help_jsonrpc(struct htable *memtable,
struct jsonrpc *jsonrpc)
{
memleak_scan_strmap(memtable, &jsonrpc->usagemap);
memleak_scan_strmap(memtable, &jsonrpc->cmdmap);
}

void jsonrpc_setup(struct lightningd *ld)
{
struct json_command **commands = get_cmdlist();
size_t num_cmdlist;
struct json_command **commands = get_cmdlist(&num_cmdlist);

ld->jsonrpc = tal(ld, struct jsonrpc);
strmap_init(&ld->jsonrpc->usagemap);
ld->jsonrpc->commands = tal_arr(ld->jsonrpc, struct json_command *, 0);
strmap_init(&ld->jsonrpc->cmdmap);
for (size_t i=0; i<num_cmdlist; i++) {
if (!jsonrpc_command_add_perm(ld, ld->jsonrpc, commands[i]))
fatal("Cannot add duplicate command %s",
Expand Down Expand Up @@ -1407,9 +1402,11 @@ void command_log(struct command *cmd, enum log_level level,

void command_set_usage(struct command *cmd, const char *usage TAKES)
{
usage = tal_strdup(cmd->ld, usage);
if (!strmap_add(&cmd->ld->jsonrpc->usagemap, cmd->json_cmd->name, usage))
fatal("Two usages for command %s?", cmd->json_cmd->name);
struct cmd_and_usage *cmd_and_usage;

cmd_and_usage = strmap_get(&cmd->ld->jsonrpc->cmdmap, cmd->json_cmd->name);
assert(!cmd_and_usage->usage);
cmd_and_usage->usage = tal_strdup(cmd_and_usage, usage);
}

bool command_check_only(const struct command *cmd)
Expand Down