mirror of
https://github.com/aljazceru/lightning.git
synced 2026-02-19 13:04:37 +01:00
lightningd/jsonrpc: Add a 'rpc_command' hook
The 'rpc_command' hook allows a plugin to take over any RPC command.
It sends the complete JSONRPC request to the plugin, which can then respond
with :
- {'continue'}: executes the command normally
- {'replace': {a_jsonrpc_request}}: replaces the request made
- {'return': {'result': {}}}: send a custom response
- {'return': {'error': {}}}: send a custom error
This way, a plugin can modify (/reimplement) or restrict the usage of
any of `lightningd`'s commands.
Changelog-Added: Plugin: A new plugin hook, `rpc_command` allows a plugin to take over `lightningd` for any RPC command.
This commit is contained in:
committed by
Christian Decker
parent
dd10b543da
commit
a0df49718a
@@ -39,11 +39,14 @@
|
||||
#include <lightningd/log.h>
|
||||
#include <lightningd/memdump.h>
|
||||
#include <lightningd/options.h>
|
||||
#include <lightningd/plugin_hook.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <wallet/db.h>
|
||||
|
||||
|
||||
/* Dummy structure. */
|
||||
struct command_result {
|
||||
@@ -575,6 +578,130 @@ struct json_stream *json_stream_fail(struct command *cmd,
|
||||
return r;
|
||||
}
|
||||
|
||||
static struct command_result *command_exec(struct json_connection *jcon,
|
||||
struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *request,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct command_result *res;
|
||||
|
||||
res = cmd->json_cmd->dispatch(cmd, buffer, request, params);
|
||||
|
||||
assert(res == ¶m_failed
|
||||
|| res == &complete
|
||||
|| res == &pending
|
||||
|| res == &unknown);
|
||||
|
||||
/* If they didn't complete it, they must call command_still_pending.
|
||||
* If they completed it, it's freed already. */
|
||||
if (res == &pending)
|
||||
assert(cmd->pending);
|
||||
|
||||
list_for_each(&jcon->commands, cmd, list)
|
||||
assert(cmd->pending);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* A plugin hook to take over (fail/alter) RPC commands */
|
||||
struct rpc_command_hook_payload {
|
||||
struct command *cmd;
|
||||
const char *buffer;
|
||||
const jsmntok_t *request;
|
||||
};
|
||||
|
||||
static void rpc_command_hook_serialize(struct rpc_command_hook_payload *p,
|
||||
struct json_stream *s)
|
||||
{
|
||||
json_object_start(s, "rpc_command");
|
||||
json_add_tok(s, "rpc_command", p->request, p->buffer);
|
||||
json_object_end(s);
|
||||
}
|
||||
|
||||
static void
|
||||
rpc_command_hook_callback(struct rpc_command_hook_payload *p,
|
||||
const char *buffer, const jsmntok_t *resulttok)
|
||||
{
|
||||
const jsmntok_t *tok, *method, *params, *custom_return, *tok_continue;
|
||||
struct json_stream *response;
|
||||
bool exec;
|
||||
|
||||
params = json_get_member(p->buffer, p->request, "params");
|
||||
|
||||
/* If no plugin registered, just continue command execution. Same if
|
||||
* the registered plugin tells us to do so. */
|
||||
if (buffer == NULL)
|
||||
return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
|
||||
p->request, params));
|
||||
else {
|
||||
tok_continue = json_get_member(buffer, resulttok, "continue");
|
||||
if (tok_continue && json_to_bool(buffer, tok_continue, &exec) && exec)
|
||||
return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
|
||||
p->request, params));
|
||||
}
|
||||
|
||||
/* If the registered plugin did not respond with continue,
|
||||
* it wants either to replace the request... */
|
||||
tok = json_get_member(buffer, resulttok, "replace");
|
||||
if (tok) {
|
||||
method = json_get_member(buffer, tok, "method");
|
||||
params = json_get_member(buffer, tok, "params");
|
||||
if (!method || !params)
|
||||
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Bad response to 'rpc_command' hook: "
|
||||
"the 'replace' object must contain a "
|
||||
"'method' and a 'params' field."));
|
||||
p->cmd->json_cmd = find_cmd(p->cmd->ld->jsonrpc, buffer, method);
|
||||
return was_pending(command_exec(p->cmd->jcon, p->cmd, buffer,
|
||||
method, params));
|
||||
}
|
||||
|
||||
/* ...or return a custom JSONRPC response. */
|
||||
tok = json_get_member(buffer, resulttok, "return");
|
||||
if (tok) {
|
||||
custom_return = json_get_member(buffer, tok, "result");
|
||||
if (custom_return) {
|
||||
response = json_start(p->cmd);
|
||||
json_add_tok(response, "result", custom_return, buffer);
|
||||
json_object_compat_end(response);
|
||||
return was_pending(command_raw_complete(p->cmd, response));
|
||||
}
|
||||
|
||||
custom_return = json_get_member(buffer, tok, "error");
|
||||
if (custom_return) {
|
||||
int code;
|
||||
const char *errmsg;
|
||||
if (!json_to_int(buffer, json_get_member(buffer, custom_return, "code"),
|
||||
&code))
|
||||
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Bad response to 'rpc_command' hook: "
|
||||
"'error' object does not contain a code."));
|
||||
errmsg = json_strdup(tmpctx, buffer,
|
||||
json_get_member(buffer, custom_return, "message"));
|
||||
if (!errmsg)
|
||||
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Bad response to 'rpc_command' hook: "
|
||||
"'error' object does not contain a message."));
|
||||
response = json_stream_fail_nodata(p->cmd, code, errmsg);
|
||||
return was_pending(command_failed(p->cmd, response));
|
||||
}
|
||||
}
|
||||
|
||||
was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST,
|
||||
"Bad response to 'rpc_command' hook."));
|
||||
}
|
||||
|
||||
REGISTER_PLUGIN_HOOK(rpc_command, rpc_command_hook_callback,
|
||||
struct rpc_command_hook_payload *,
|
||||
rpc_command_hook_serialize,
|
||||
struct rpc_command_hook_payload *);
|
||||
|
||||
static void call_rpc_command_hook(struct rpc_command_hook_payload *p)
|
||||
{
|
||||
plugin_hook_call_rpc_command(p->cmd->ld, p, p);
|
||||
}
|
||||
|
||||
/* We return struct command_result so command_fail return value has a natural
|
||||
* sink; we don't actually use the result. */
|
||||
static struct command_result *
|
||||
@@ -582,7 +709,7 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[])
|
||||
{
|
||||
const jsmntok_t *method, *id, *params;
|
||||
struct command *c;
|
||||
struct command_result *res;
|
||||
struct rpc_command_hook_payload *rpc_hook;
|
||||
|
||||
if (tok[0].type != JSMN_OBJECT) {
|
||||
json_command_malformed(jcon, "null",
|
||||
@@ -641,22 +768,18 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[])
|
||||
jcon->buffer + method->start);
|
||||
}
|
||||
|
||||
db_begin_transaction(jcon->ld->wallet->db);
|
||||
res = c->json_cmd->dispatch(c, jcon->buffer, tok, params);
|
||||
db_commit_transaction(jcon->ld->wallet->db);
|
||||
rpc_hook = tal(c, struct rpc_command_hook_payload);
|
||||
rpc_hook->cmd = c;
|
||||
/* Duplicate since we might outlive the connection */
|
||||
rpc_hook->buffer = tal_dup_arr(rpc_hook, char, jcon->buffer,
|
||||
tal_count(jcon->buffer), 0);
|
||||
rpc_hook->request = tal_dup_arr(rpc_hook, const jsmntok_t, tok,
|
||||
tal_count(tok), 0);
|
||||
/* Prevent a race between was_pending and still_pending */
|
||||
new_reltimer(c->ld->timers, rpc_hook, time_from_msec(1),
|
||||
call_rpc_command_hook, rpc_hook);
|
||||
|
||||
assert(res == ¶m_failed
|
||||
|| res == &complete
|
||||
|| res == &pending
|
||||
|| res == &unknown);
|
||||
|
||||
/* If they didn't complete it, they must call command_still_pending.
|
||||
* If they completed it, it's freed already. */
|
||||
if (res == &pending)
|
||||
assert(c->pending);
|
||||
list_for_each(&jcon->commands, c, list)
|
||||
assert(c->pending);
|
||||
return res;
|
||||
return command_still_pending(c);
|
||||
}
|
||||
|
||||
/* Mutual recursion */
|
||||
|
||||
@@ -102,6 +102,10 @@ struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name
|
||||
const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED,
|
||||
const jsmntok_t **out UNNEEDED)
|
||||
{ fprintf(stderr, "param_tok called!\n"); abort(); }
|
||||
/* Generated stub for plugin_hook_call_ */
|
||||
void plugin_hook_call_(struct lightningd *ld UNNEEDED, const struct plugin_hook *hook UNNEEDED,
|
||||
void *payload UNNEEDED, void *cb_arg UNNEEDED)
|
||||
{ fprintf(stderr, "plugin_hook_call_ called!\n"); abort(); }
|
||||
/* AUTOGENERATED MOCKS END */
|
||||
|
||||
bool deprecated_apis;
|
||||
|
||||
Reference in New Issue
Block a user