plugins: make rpc_command hook chainable

Changelog-Changed: The `rpc_command` hook is now chainable.
This commit is contained in:
Michael Schmoock 2021-01-30 14:35:52 +01:00 committed by Rusty Russell
parent 48e91da829
commit afaaeb3c7d

View File

@ -665,6 +665,12 @@ struct rpc_command_hook_payload {
struct command *cmd; struct command *cmd;
const char *buffer; const char *buffer;
const jsmntok_t *request; const jsmntok_t *request;
/* custom response/replace/error options plugins can have */
const char *custom_result;
const char *custom_error;
const jsmntok_t *custom_replace;
const char *custom_buffer;
}; };
static void rpc_command_hook_serialize(struct rpc_command_hook_payload *p, static void rpc_command_hook_serialize(struct rpc_command_hook_payload *p,
@ -734,50 +740,89 @@ fail:
"Bad response to 'rpc_command' hook: %s", bad)); "Bad response to 'rpc_command' hook: %s", bad));
} }
static void static void rpc_command_hook_final(struct rpc_command_hook_payload *p STEALS)
rpc_command_hook_callback(struct rpc_command_hook_payload *p STEALS,
const char *buffer, const jsmntok_t *resulttok)
{ {
const jsmntok_t *tok, *params, *custom_return; const jsmntok_t *params;
const jsmntok_t *innerresulttok;
struct json_stream *response;
/* Free payload with cmd */ /* Free payload with cmd */
tal_steal(p->cmd, p); tal_steal(p->cmd, p);
if (p->custom_result != NULL) {
struct json_stream *s = json_start(p->cmd);
json_add_jsonstr(s, "result", p->custom_result);
json_object_compat_end(s);
return was_pending(command_raw_complete(p->cmd, s));
}
if (p->custom_error != NULL) {
struct json_stream *s = json_start(p->cmd);
json_add_jsonstr(s, "error", p->custom_error);
json_object_compat_end(s);
return was_pending(command_raw_complete(p->cmd, s));
}
if (p->custom_replace != NULL)
return replace_command(p, p->custom_buffer, p->custom_replace);
/* If no plugin requested a change, just continue command execution. */
params = json_get_member(p->buffer, p->request, "params"); params = json_get_member(p->buffer, p->request, "params");
return was_pending(command_exec(p->cmd->jcon,
p->cmd,
p->buffer,
p->request,
params));
}
/* If no plugin registered, just continue command execution. Same if static bool
* the registered plugin tells us to do so. */ rpc_command_hook_callback(struct rpc_command_hook_payload *p,
if (buffer == NULL) const char *buffer, const jsmntok_t *resulttok)
return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer, {
p->request, params)); const struct lightningd *ld = p->cmd->ld;
const jsmntok_t *tok, *custom_return;
static char *error = "";
char *method;
innerresulttok = json_get_member(buffer, resulttok, "result"); if (!resulttok || !buffer)
if (innerresulttok) { return true;
if (json_tok_streq(buffer, innerresulttok, "continue")) {
return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer, tok = json_get_member(buffer, resulttok, "result");
p->request, params)); if (tok) {
if (!json_tok_streq(buffer, tok, "continue")) {
error = "'result' should only be 'continue'.";
goto log_error_and_skip;
} }
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, /* plugin tells us to do nothing. just pass. */
"Bad 'result' to 'rpc_command' hook.")); return true;
}
/* didn't just continue but hook was already modified by prior plugin */
if (p->custom_result != NULL ||
p->custom_error != NULL ||
p->custom_replace != NULL) {
/* get method name and log error (only the first time). */
tok = json_get_member(p->buffer, p->request, "method");
method = tal_strndup(p, p->buffer + tok->start, tok->end - tok->start);
log_unusual(ld->log, "rpc_command hook '%s' already modified, ignoring.", method );
rpc_command_hook_final(p);
return false;
} }
/* If the registered plugin did not respond with continue, /* If the registered plugin did not respond with continue,
* it wants either to replace the request... */ * it wants either to replace the request... */
tok = json_get_member(buffer, resulttok, "replace"); tok = json_get_member(buffer, resulttok, "replace");
if (tok) if (tok) {
return replace_command(p, buffer, tok); /* We need to make copies here, as buffer and tokens
* can be reused. */
p->custom_replace = json_tok_copy(p, tok);
p->custom_buffer = tal_dup_talarr(p, char, buffer);
return true;
}
/* ...or return a custom JSONRPC response. */ /* ...or return a custom JSONRPC response. */
tok = json_get_member(buffer, resulttok, "return"); tok = json_get_member(buffer, resulttok, "return");
if (tok) { if (tok) {
custom_return = json_get_member(buffer, tok, "result"); custom_return = json_get_member(buffer, tok, "result");
if (custom_return) { if (custom_return) {
response = json_start(p->cmd); p->custom_result = json_strdup(p, buffer, custom_return);
json_add_tok(response, "result", custom_return, buffer); return true;
json_object_compat_end(response);
return was_pending(command_raw_complete(p->cmd, response));
} }
custom_return = json_get_member(buffer, tok, "error"); custom_return = json_get_member(buffer, tok, "error");
@ -786,29 +831,32 @@ rpc_command_hook_callback(struct rpc_command_hook_payload *p STEALS,
const char *errmsg; const char *errmsg;
if (!json_to_errcode(buffer, if (!json_to_errcode(buffer,
json_get_member(buffer, custom_return, "code"), json_get_member(buffer, custom_return, "code"),
&code)) &code)) {
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, error = "'error' object does not contain a code.";
"Bad response to 'rpc_command' hook: " goto log_error_and_skip;
"'error' object does not contain a code.")); }
errmsg = json_strdup(tmpctx, buffer, errmsg = json_strdup(tmpctx, buffer,
json_get_member(buffer, custom_return, "message")); json_get_member(buffer, custom_return, "message"));
if (!errmsg) if (!errmsg) {
return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, error = "'error' object does not contain a message.";
"Bad response to 'rpc_command' hook: " goto log_error_and_skip;
"'error' object does not contain a message.")); }
response = json_stream_fail_nodata(p->cmd, code, errmsg); p->custom_error = json_strdup(p, buffer, custom_return);
return was_pending(command_failed(p->cmd, response)); return true;
} }
} }
was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, log_error_and_skip:
"Bad response to 'rpc_command' hook.")); /* Just log BROKEN errors. Give other plugins a chance. */
log_broken(ld->log, "Bad response to 'rpc_command' hook. %s", error);
return true;
} }
REGISTER_SINGLE_PLUGIN_HOOK(rpc_command, REGISTER_PLUGIN_HOOK(rpc_command,
rpc_command_hook_callback, rpc_command_hook_callback,
rpc_command_hook_serialize, rpc_command_hook_final,
struct rpc_command_hook_payload *); rpc_command_hook_serialize,
struct rpc_command_hook_payload *);
/* We return struct command_result so command_fail return value has a natural /* We return struct command_result so command_fail return value has a natural
* sink; we don't actually use the result. */ * sink; we don't actually use the result. */
@ -884,6 +932,12 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[])
rpc_hook->buffer = tal_dup_talarr(rpc_hook, char, jcon->buffer); rpc_hook->buffer = tal_dup_talarr(rpc_hook, char, jcon->buffer);
rpc_hook->request = tal_dup_talarr(rpc_hook, jsmntok_t, tok); rpc_hook->request = tal_dup_talarr(rpc_hook, jsmntok_t, tok);
/* NULL the custom_ values for the hooks */
rpc_hook->custom_result = NULL;
rpc_hook->custom_error = NULL;
rpc_hook->custom_replace = NULL;
rpc_hook->custom_buffer = NULL;
db_begin_transaction(jcon->ld->wallet->db); db_begin_transaction(jcon->ld->wallet->db);
completed = plugin_hook_call_rpc_command(jcon->ld, rpc_hook); completed = plugin_hook_call_rpc_command(jcon->ld, rpc_hook);
db_commit_transaction(jcon->ld->wallet->db); db_commit_transaction(jcon->ld->wallet->db);