|
|
|
/* Code for JSON_RPC API */
|
|
|
|
/* eg: { "method" : "dev-echo", "params" : [ "hello", "Arabella!" ], "id" : "1" } */
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ccan/array_size/array_size.h>
|
|
|
|
#include <ccan/err/err.h>
|
|
|
|
#include <ccan/io/io.h>
|
|
|
|
#include <ccan/str/hex/hex.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
#include <common/json.h>
|
|
|
|
#include <common/memleak.h>
|
|
|
|
#include <common/version.h>
|
|
|
|
#include <common/wireaddr.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <lightningd/chaintopology.h>
|
|
|
|
#include <lightningd/jsonrpc.h>
|
|
|
|
#include <lightningd/jsonrpc_errors.h>
|
|
|
|
#include <lightningd/lightningd.h>
|
|
|
|
#include <lightningd/log.h>
|
|
|
|
#include <lightningd/options.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
|
|
|
|
struct json_output {
|
|
|
|
struct list_node list;
|
|
|
|
const char *json;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* jcon and cmd have separate lifetimes: we detach them on either destruction */
|
|
|
|
static void destroy_jcon(struct json_connection *jcon)
|
|
|
|
{
|
|
|
|
struct command *cmd;
|
|
|
|
|
|
|
|
list_for_each(&jcon->commands, cmd, list) {
|
|
|
|
log_unusual(jcon->log, "Abandoning command");
|
|
|
|
cmd->jcon = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure this happens last! */
|
|
|
|
tal_free(jcon->log);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void destroy_cmd(struct command *cmd)
|
|
|
|
{
|
|
|
|
if (cmd->jcon)
|
|
|
|
list_del_from(&cmd->jcon->commands, &cmd->list);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_help(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t *params);
|
|
|
|
|
|
|
|
static const struct json_command help_command = {
|
|
|
|
"help",
|
|
|
|
json_help,
|
|
|
|
"List available commands, or give verbose help on one command.",
|
|
|
|
|
|
|
|
.verbose = "help [command]\n"
|
|
|
|
"Without [command]:\n"
|
|
|
|
" Outputs an array of objects with 'command' and 'description'\n"
|
|
|
|
"With [command]:\n"
|
|
|
|
" Give a single object containing 'verbose', which completely describes\n"
|
|
|
|
" the command inputs and outputs."
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &help_command);
|
|
|
|
|
|
|
|
static void json_stop(struct command *cmd,
|
|
|
|
const char *buffer UNUSED, const jsmntok_t *params UNUSED)
|
|
|
|
{
|
|
|
|
struct json_result *response = new_json_result(cmd);
|
|
|
|
|
|
|
|
/* This can't have closed yet! */
|
|
|
|
cmd->jcon->stop = true;
|
|
|
|
json_add_string(response, NULL, "Shutting down");
|
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command stop_command = {
|
|
|
|
"stop",
|
|
|
|
json_stop,
|
|
|
|
"Shut down the lightningd process"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &stop_command);
|
|
|
|
|
|
|
|
#if DEVELOPER
|
|
|
|
static void json_rhash(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct json_result *response = new_json_result(cmd);
|
|
|
|
jsmntok_t *secrettok;
|
|
|
|
struct sha256 secret;
|
|
|
|
|
|
|
|
if (!json_get_params(cmd, buffer, params,
|
|
|
|
"secret", &secrettok,
|
|
|
|
NULL)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hex_decode(buffer + secrettok->start,
|
|
|
|
secrettok->end - secrettok->start,
|
|
|
|
&secret, sizeof(secret))) {
|
|
|
|
command_fail(cmd, "'%.*s' is not a valid 32-byte hex value",
|
|
|
|
secrettok->end - secrettok->start,
|
|
|
|
buffer + secrettok->start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hash in place. */
|
|
|
|
sha256(&secret, &secret, sizeof(secret));
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
json_add_hex(response, "rhash", &secret, sizeof(secret));
|
|
|
|
json_object_end(response);
|
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command dev_rhash_command = {
|
|
|
|
"dev-rhash",
|
|
|
|
json_rhash,
|
|
|
|
"Show SHA256 of {secret}"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &dev_rhash_command);
|
|
|
|
|
|
|
|
static void json_crash(struct command *cmd UNUSED,
|
|
|
|
const char *buffer UNUSED, const jsmntok_t *params UNUSED)
|
|
|
|
{
|
|
|
|
fatal("Crash at user request");
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command dev_crash_command = {
|
|
|
|
"dev-crash",
|
|
|
|
json_crash,
|
|
|
|
"Crash lightningd by calling fatal()"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &dev_crash_command);
|
|
|
|
#endif /* DEVELOPER */
|
|
|
|
|
|
|
|
static void json_getinfo(struct command *cmd,
|
|
|
|
const char *buffer UNUSED, const jsmntok_t *params UNUSED)
|
|
|
|
{
|
|
|
|
struct json_result *response = new_json_result(cmd);
|
|
|
|
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
json_add_pubkey(response, "id", &cmd->ld->id);
|
|
|
|
if (cmd->ld->portnum) {
|
|
|
|
json_add_num(response, "port", cmd->ld->portnum);
|
|
|
|
json_array_start(response, "address");
|
|
|
|
for (size_t i = 0; i < tal_count(cmd->ld->wireaddrs); i++)
|
|
|
|
json_add_address(response, NULL, cmd->ld->wireaddrs+i);
|
|
|
|
json_array_end(response);
|
|
|
|
}
|
|
|
|
json_add_string(response, "version", version());
|
|
|
|
json_add_num(response, "blockheight", get_block_height(cmd->ld->topology));
|
|
|
|
json_add_string(response, "network", get_chainparams(cmd->ld)->network_name);
|
|
|
|
json_object_end(response);
|
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command getinfo_command = {
|
|
|
|
"getinfo",
|
|
|
|
json_getinfo,
|
|
|
|
"Show information about this node"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &getinfo_command);
|
|
|
|
|
|
|
|
static size_t num_cmdlist;
|
|
|
|
|
|
|
|
static struct json_command **get_cmdlist(void)
|
|
|
|
{
|
|
|
|
static struct json_command **cmdlist;
|
|
|
|
if (!cmdlist)
|
|
|
|
cmdlist = autodata_get(json_command, &num_cmdlist);
|
|
|
|
|
|
|
|
return cmdlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_help(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
struct json_result *response = new_json_result(cmd);
|
|
|
|
struct json_command **cmdlist = get_cmdlist();
|
|
|
|
jsmntok_t *cmdtok;
|
|
|
|
|
|
|
|
if (!json_get_params(cmd, buffer, params, "?command", &cmdtok, NULL)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
if (cmdtok) {
|
|
|
|
for (i = 0; i < num_cmdlist; i++) {
|
|
|
|
if (json_tok_streq(buffer, cmdtok, cmdlist[i]->name)) {
|
|
|
|
if (!cmdlist[i]->verbose)
|
|
|
|
json_add_string(response,
|
|
|
|
"verbose",
|
|
|
|
"HELP! Please contribute"
|
|
|
|
" a description for this"
|
|
|
|
" command!");
|
|
|
|
else
|
|
|
|
json_add_string_escape(response,
|
|
|
|
"verbose",
|
|
|
|
cmdlist[i]->verbose);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
command_fail(cmd, "Unknown command '%.*s'",
|
|
|
|
cmdtok->end - cmdtok->start,
|
|
|
|
buffer + cmdtok->start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
json_array_start(response, "help");
|
|
|
|
for (i = 0; i < num_cmdlist; i++) {
|
|
|
|
json_add_object(response,
|
|
|
|
"command", JSMN_STRING,
|
|
|
|
cmdlist[i]->name,
|
|
|
|
"description", JSMN_STRING,
|
|
|
|
cmdlist[i]->description,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
json_array_end(response);
|
|
|
|
|
|
|
|
done:
|
|
|
|
json_object_end(response);
|
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command *find_cmd(const char *buffer,
|
|
|
|
const jsmntok_t *tok)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
struct json_command **cmdlist = get_cmdlist();
|
|
|
|
|
|
|
|
/* cmdlist[i]->name can be NULL in test code. */
|
|
|
|
for (i = 0; i < num_cmdlist; i++)
|
|
|
|
if (cmdlist[i]->name
|
|
|
|
&& json_tok_streq(buffer, tok, cmdlist[i]->name))
|
|
|
|
return cmdlist[i];
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_done(struct json_connection *jcon,
|
|
|
|
struct command *cmd,
|
|
|
|
const char *json TAKES)
|
|
|
|
{
|
|
|
|
struct json_output *out = tal(jcon, struct json_output);
|
|
|
|
out->json = tal_strdup(out, json);
|
|
|
|
|
|
|
|
tal_free(cmd);
|
|
|
|
|
|
|
|
/* Queue for writing, and wake writer. */
|
|
|
|
list_add_tail(&jcon->output, &out->list);
|
|
|
|
io_wake(jcon);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void connection_complete_ok(struct json_connection *jcon,
|
|
|
|
struct command *cmd,
|
|
|
|
const char *id,
|
|
|
|
const struct json_result *result)
|
|
|
|
{
|
|
|
|
/* This JSON is simple enough that we build manually */
|
|
|
|
json_done(jcon, cmd, take(tal_fmt(jcon,
|
|
|
|
"{ \"jsonrpc\": \"2.0\", "
|
|
|
|
"\"result\" : %s,"
|
|
|
|
" \"id\" : %s }\n",
|
|
|
|
json_result_string(result), id)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void connection_complete_error(struct json_connection *jcon,
|
|
|
|
struct command *cmd,
|
|
|
|
const char *id,
|
|
|
|
const char *errmsg,
|
|
|
|
int code,
|
|
|
|
const struct json_result *data)
|
|
|
|
{
|
|
|
|
const tal_t *tmpctx = tal_tmpctx(jcon);
|
|
|
|
/* Use this to escape errmsg. */
|
|
|
|
struct json_result *errorres = new_json_result(tmpctx);
|
|
|
|
const char *data_str;
|
|
|
|
|
|
|
|
json_add_string_escape(errorres, NULL, errmsg);
|
|
|
|
if (data)
|
|
|
|
data_str = tal_fmt(tmpctx, ", \"data\" : %s",
|
|
|
|
json_result_string(data));
|
|
|
|
else
|
|
|
|
data_str = "";
|
|
|
|
|
|
|
|
json_done(jcon, cmd, take(tal_fmt(tmpctx,
|
|
|
|
"{ \"jsonrpc\": \"2.0\", "
|
|
|
|
" \"error\" : "
|
|
|
|
"{ \"code\" : %d,"
|
|
|
|
" \"message\" : %s%s },"
|
|
|
|
" \"id\" : %s }\n",
|
|
|
|
code,
|
|
|
|
json_result_string(errorres),
|
|
|
|
data_str,
|
|
|
|
id)));
|
|
|
|
tal_free(tmpctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct json_result *null_response(const tal_t *ctx)
|
|
|
|
{
|
|
|
|
struct json_result *response;
|
|
|
|
|
|
|
|
response = new_json_result(ctx);
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
json_object_end(response);
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_pubkey(struct json_result *response,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct pubkey *key)
|
|
|
|
{
|
|
|
|
u8 der[PUBKEY_DER_LEN];
|
|
|
|
|
|
|
|
pubkey_to_der(der, key);
|
|
|
|
json_add_hex(response, fieldname, der, sizeof(der));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_txid(struct json_result *result, const char *fieldname,
|
|
|
|
const struct bitcoin_txid *txid)
|
|
|
|
{
|
|
|
|
char hex[hex_str_size(sizeof(*txid))];
|
|
|
|
|
|
|
|
bitcoin_txid_to_hex(txid, hex, sizeof(hex));
|
|
|
|
json_add_string(result, fieldname, hex);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool json_tok_pubkey(const char *buffer, const jsmntok_t *tok,
|
|
|
|
struct pubkey *pubkey)
|
|
|
|
{
|
|
|
|
return pubkey_from_hexstr(buffer + tok->start,
|
|
|
|
tok->end - tok->start, pubkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_short_channel_id(struct json_result *response,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct short_channel_id *id)
|
|
|
|
{
|
|
|
|
json_add_string(response, fieldname,
|
|
|
|
type_to_string(response, struct short_channel_id, id));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool json_tok_short_channel_id(const char *buffer, const jsmntok_t *tok,
|
|
|
|
struct short_channel_id *scid)
|
|
|
|
{
|
|
|
|
return short_channel_id_from_str(buffer + tok->start,
|
|
|
|
tok->end - tok->start,
|
|
|
|
scid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_address(struct json_result *response, const char *fieldname,
|
|
|
|
const struct wireaddr *addr)
|
|
|
|
{
|
|
|
|
/* No need to print padding */
|
|
|
|
if (addr->type == ADDR_TYPE_PADDING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
json_object_start(response, fieldname);
|
|
|
|
char *addrstr = tal_arr(response, char, INET6_ADDRSTRLEN);
|
|
|
|
if (addr->type == ADDR_TYPE_IPV4) {
|
|
|
|
inet_ntop(AF_INET, addr->addr, addrstr, INET_ADDRSTRLEN);
|
|
|
|
json_add_string(response, "type", "ipv4");
|
|
|
|
json_add_string(response, "address", addrstr);
|
|
|
|
json_add_num(response, "port", addr->port);
|
|
|
|
} else if (addr->type == ADDR_TYPE_IPV6) {
|
|
|
|
inet_ntop(AF_INET6, addr->addr, addrstr, INET6_ADDRSTRLEN);
|
|
|
|
json_add_string(response, "type", "ipv6");
|
|
|
|
json_add_string(response, "address", addrstr);
|
|
|
|
json_add_num(response, "port", addr->port);
|
|
|
|
}
|
|
|
|
json_object_end(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool cmd_in_jcon(const struct json_connection *jcon,
|
|
|
|
const struct command *cmd)
|
|
|
|
{
|
|
|
|
const struct command *i;
|
|
|
|
|
|
|
|
list_for_each(&jcon->commands, i, list)
|
|
|
|
if (i == cmd)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void command_success(struct command *cmd, struct json_result *result)
|
|
|
|
{
|
|
|
|
struct json_connection *jcon = cmd->jcon;
|
|
|
|
|
|
|
|
if (!jcon) {
|
|
|
|
log_unusual(cmd->ld->log,
|
|
|
|
"Command returned result after jcon close");
|
|
|
|
tal_free(cmd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(cmd_in_jcon(jcon, cmd));
|
|
|
|
connection_complete_ok(jcon, cmd, cmd->id, result);
|
|
|
|
log_debug(jcon->log, "Success");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void command_fail_v(struct command *cmd,
|
|
|
|
int code,
|
|
|
|
const struct json_result *data,
|
|
|
|
const char *fmt, va_list ap)
|
|
|
|
{
|
|
|
|
char *error;
|
|
|
|
struct json_connection *jcon = cmd->jcon;
|
|
|
|
|
|
|
|
if (!jcon) {
|
|
|
|
log_unusual(cmd->ld->log,
|
|
|
|
"Command failed after jcon close");
|
|
|
|
tal_free(cmd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = tal_vfmt(cmd, fmt, ap);
|
|
|
|
|
|
|
|
log_debug(jcon->log, "Failing: %s", error);
|
|
|
|
|
|
|
|
assert(cmd_in_jcon(jcon, cmd));
|
|
|
|
connection_complete_error(jcon, cmd, cmd->id, error, code, data);
|
|
|
|
}
|
|
|
|
void command_fail(struct command *cmd, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
command_fail_v(cmd, -1, NULL, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
void command_fail_detailed(struct command *cmd,
|
|
|
|
int code, const struct json_result *data,
|
|
|
|
const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
command_fail_v(cmd, code, data, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void command_still_pending(struct command *cmd)
|
|
|
|
{
|
|
|
|
notleak_with_children(cmd);
|
|
|
|
notleak(cmd->jcon);
|
|
|
|
cmd->pending = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_command_malformed(struct json_connection *jcon,
|
|
|
|
const char *id,
|
|
|
|
const char *error)
|
|
|
|
{
|
|
|
|
return connection_complete_error(jcon, NULL, id, error,
|
|
|
|
JSONRPC2_INVALID_REQUEST, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void parse_request(struct json_connection *jcon, const jsmntok_t tok[])
|
|
|
|
{
|
|
|
|
const jsmntok_t *method, *id, *params;
|
|
|
|
const struct json_command *cmd;
|
|
|
|
struct command *c;
|
|
|
|
|
|
|
|
if (tok[0].type != JSMN_OBJECT) {
|
|
|
|
json_command_malformed(jcon, "null",
|
|
|
|
"Expected {} for json command");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
method = json_get_member(jcon->buffer, tok, "method");
|
|
|
|
params = json_get_member(jcon->buffer, tok, "params");
|
|
|
|
id = json_get_member(jcon->buffer, tok, "id");
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
json_command_malformed(jcon, "null", "No id");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (id->type != JSMN_STRING && id->type != JSMN_PRIMITIVE) {
|
|
|
|
json_command_malformed(jcon, "null",
|
|
|
|
"Expected string/primitive for id");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is a convenient tal parent for duration of command
|
|
|
|
* (which may outlive the conn!). */
|
|
|
|
c = tal(jcon->ld, struct command);
|
|
|
|
c->jcon = jcon;
|
|
|
|
c->ld = jcon->ld;
|
|
|
|
c->pending = false;
|
|
|
|
c->id = tal_strndup(c,
|
|
|
|
json_tok_contents(jcon->buffer, id),
|
|
|
|
json_tok_len(id));
|
|
|
|
list_add(&jcon->commands, &c->list);
|
|
|
|
tal_add_destructor(c, destroy_cmd);
|
|
|
|
|
|
|
|
if (!method || !params) {
|
|
|
|
command_fail_detailed(c,
|
|
|
|
JSONRPC2_INVALID_REQUEST, NULL,
|
|
|
|
method ? "No params" : "No method");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method->type != JSMN_STRING) {
|
|
|
|
command_fail_detailed(c,
|
|
|
|
JSONRPC2_INVALID_REQUEST, NULL,
|
|
|
|
"Expected string for method");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd = find_cmd(jcon->buffer, method);
|
|
|
|
if (!cmd) {
|
|
|
|
command_fail_detailed(c,
|
|
|
|
JSONRPC2_METHOD_NOT_FOUND, NULL,
|
|
|
|
"Unknown command '%.*s'",
|
|
|
|
method->end - method->start,
|
|
|
|
jcon->buffer + method->start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cmd->deprecated && !deprecated_apis) {
|
|
|
|
command_fail_detailed(c,
|
|
|
|
JSONRPC2_METHOD_NOT_FOUND, NULL,
|
|
|
|
"Command '%.*s' is deprecated",
|
|
|
|
method->end - method->start,
|
|
|
|
jcon->buffer + method->start);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
db_begin_transaction(jcon->ld->wallet->db);
|
|
|
|
cmd->dispatch(c, jcon->buffer, params);
|
|
|
|
db_commit_transaction(jcon->ld->wallet->db);
|
|
|
|
|
|
|
|
/* If they didn't complete it, they must call command_still_pending */
|
|
|
|
if (cmd_in_jcon(jcon, c))
|
|
|
|
assert(c->pending);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool json_get_params(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t param[], ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
const char **names;
|
|
|
|
size_t num_names;
|
|
|
|
/* Uninitialized warnings on p and end */
|
|
|
|
const jsmntok_t **tokptr, *p = NULL, *end = NULL;
|
|
|
|
|
|
|
|
if (param->type == JSMN_ARRAY) {
|
|
|
|
if (param->size == 0)
|
|
|
|
p = NULL;
|
|
|
|
else
|
|
|
|
p = param + 1;
|
|
|
|
end = json_next(param);
|
|
|
|
} else if (param->type != JSMN_OBJECT) {
|
|
|
|
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, NULL,
|
|
|
|
"Expected array or object for params");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
num_names = 0;
|
|
|
|
names = tal_arr(cmd, const char *, num_names + 1);
|
|
|
|
va_start(ap, param);
|
|
|
|
while ((names[num_names] = va_arg(ap, const char *)) != NULL) {
|
|
|
|
tokptr = va_arg(ap, const jsmntok_t **);
|
|
|
|
bool compulsory = true;
|
|
|
|
if (names[num_names][0] == '?') {
|
|
|
|
names[num_names]++;
|
|
|
|
compulsory = false;
|
|
|
|
}
|
|
|
|
if (param->type == JSMN_ARRAY) {
|
|
|
|
*tokptr = p;
|
|
|
|
if (p) {
|
|
|
|
p = json_next(p);
|
|
|
|
if (p == end)
|
|
|
|
p = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*tokptr = json_get_member(buffer, param,
|
|
|
|
names[num_names]);
|
|
|
|
}
|
|
|
|
/* Convert 'null' to NULL */
|
|
|
|
if (*tokptr
|
|
|
|
&& (*tokptr)->type == JSMN_PRIMITIVE
|
|
|
|
&& buffer[(*tokptr)->start] == 'n') {
|
|
|
|
*tokptr = NULL;
|
|
|
|
}
|
|
|
|
if (compulsory && !*tokptr) {
|
|
|
|
va_end(ap);
|
|
|
|
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, NULL,
|
|
|
|
"Missing '%s' parameter",
|
|
|
|
names[num_names]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
num_names++;
|
|
|
|
tal_resize(&names, num_names + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
/* Now make sure there aren't any params which aren't valid */
|
|
|
|
if (param->type == JSMN_ARRAY) {
|
|
|
|
if (param->size > num_names) {
|
|
|
|
tal_free(names);
|
|
|
|
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, NULL,
|
|
|
|
"Too many parameters:"
|
|
|
|
" got %u, expected %zu",
|
|
|
|
param->size, num_names);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const jsmntok_t *t;
|
|
|
|
|
|
|
|
end = json_next(param);
|
|
|
|
|
|
|
|
/* Find each parameter among the valid names */
|
|
|
|
for (t = param + 1; t < end; t = json_next(t+1)) {
|
|
|
|
bool found = false;
|
|
|
|
for (size_t i = 0; i < num_names; i++) {
|
|
|
|
if (json_tok_streq(buffer, t, names[i]))
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
tal_free(names);
|
|
|
|
command_fail_detailed(cmd,
|
|
|
|
JSONRPC2_INVALID_PARAMS,
|
|
|
|
NULL,
|
|
|
|
"Unknown parameter '%.*s'",
|
|
|
|
t->end - t->start,
|
|
|
|
buffer + t->start);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tal_free(names);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct io_plan *write_json(struct io_conn *conn,
|
|
|
|
struct json_connection *jcon)
|
|
|
|
{
|
|
|
|
struct json_output *out;
|
|
|
|
|
|
|
|
out = list_pop(&jcon->output, struct json_output, list);
|
|
|
|
if (!out) {
|
|
|
|
if (jcon->stop) {
|
|
|
|
log_unusual(jcon->log, "JSON-RPC shutdown");
|
|
|
|
/* Return us to toplevel lightningd.c */
|
|
|
|
io_break(jcon->ld);
|
|
|
|
return io_close(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wait for more output. */
|
|
|
|
return io_out_wait(conn, jcon, write_json, jcon);
|
|
|
|
}
|
|
|
|
|
|
|
|
jcon->outbuf = tal_steal(jcon, out->json);
|
|
|
|
tal_free(out);
|
|
|
|
|
|
|
|
log_io(jcon->log, LOG_IO_OUT, "", jcon->outbuf, strlen(jcon->outbuf));
|
|
|
|
return io_write(conn,
|
|
|
|
jcon->outbuf, strlen(jcon->outbuf), write_json, jcon);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct io_plan *read_json(struct io_conn *conn,
|
|
|
|
struct json_connection *jcon)
|
|
|
|
{
|
|
|
|
jsmntok_t *toks;
|
|
|
|
bool valid;
|
|
|
|
|
|
|
|
log_io(jcon->log, LOG_IO_IN, "",
|
|
|
|
jcon->buffer + jcon->used, jcon->len_read);
|
|
|
|
|
|
|
|
/* Resize larger if we're full. */
|
|
|
|
jcon->used += jcon->len_read;
|
|
|
|
if (jcon->used == tal_count(jcon->buffer))
|
|
|
|
tal_resize(&jcon->buffer, jcon->used * 2);
|
|
|
|
|
|
|
|
again:
|
|
|
|
toks = json_parse_input(jcon->buffer, jcon->used, &valid);
|
|
|
|
if (!toks) {
|
|
|
|
if (!valid) {
|
|
|
|
log_unusual(jcon->ld->log,
|
|
|
|
"Invalid token in json input: '%.*s'",
|
|
|
|
(int)jcon->used, jcon->buffer);
|
|
|
|
json_command_malformed(
|
|
|
|
jcon, NULL,
|
|
|
|
"Invalid token in json input");
|
|
|
|
return io_halfclose(conn);
|
|
|
|
}
|
|
|
|
/* We need more. */
|
|
|
|
goto read_more;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Empty buffer? (eg. just whitespace). */
|
|
|
|
if (tal_count(toks) == 1) {
|
|
|
|
jcon->used = 0;
|
|
|
|
goto read_more;
|
|
|
|
}
|
|
|
|
|
|
|
|
parse_request(jcon, toks);
|
|
|
|
|
|
|
|
/* Remove first {}. */
|
|
|
|
memmove(jcon->buffer, jcon->buffer + toks[0].end,
|
|
|
|
tal_count(jcon->buffer) - toks[0].end);
|
|
|
|
jcon->used -= toks[0].end;
|
|
|
|
tal_free(toks);
|
|
|
|
|
|
|
|
/* See if we can parse the rest. */
|
|
|
|
goto again;
|
|
|
|
|
|
|
|
read_more:
|
|
|
|
tal_free(toks);
|
|
|
|
return io_read_partial(conn, jcon->buffer + jcon->used,
|
|
|
|
tal_count(jcon->buffer) - jcon->used,
|
|
|
|
&jcon->len_read, read_json, jcon);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct io_plan *jcon_connected(struct io_conn *conn,
|
|
|
|
struct lightningd *ld)
|
|
|
|
{
|
|
|
|
struct json_connection *jcon;
|
|
|
|
|
|
|
|
jcon = tal(conn, struct json_connection);
|
|
|
|
jcon->ld = ld;
|
|
|
|
jcon->used = 0;
|
|
|
|
jcon->buffer = tal_arr(jcon, char, 64);
|
|
|
|
jcon->stop = false;
|
|
|
|
list_head_init(&jcon->commands);
|
|
|
|
|
|
|
|
/* We want to log on destruction, so we free this in destructor. */
|
|
|
|
jcon->log = new_log(ld->log_book, ld->log_book, "%sjcon fd %i:",
|
|
|
|
log_prefix(ld->log), io_conn_fd(conn));
|
|
|
|
list_head_init(&jcon->output);
|
|
|
|
|
|
|
|
tal_add_destructor(jcon, destroy_jcon);
|
|
|
|
|
|
|
|
return io_duplex(conn,
|
|
|
|
io_read_partial(conn, jcon->buffer,
|
|
|
|
tal_count(jcon->buffer),
|
|
|
|
&jcon->len_read, read_json, jcon),
|
|
|
|
write_json(conn, jcon));
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct io_plan *incoming_jcon_connected(struct io_conn *conn,
|
|
|
|
struct lightningd *ld)
|
|
|
|
{
|
|
|
|
log_debug(ld->log, "Connected json input");
|
|
|
|
|
|
|
|
/* Lifetime of JSON conn is limited to fd connect time. */
|
|
|
|
return jcon_connected(notleak(conn), ld);
|
|
|
|
}
|
|
|
|
|
|
|
|
void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename)
|
|
|
|
{
|
|
|
|
struct sockaddr_un addr;
|
|
|
|
int fd, old_umask;
|
|
|
|
|
|
|
|
if (streq(rpc_filename, ""))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (streq(rpc_filename, "/dev/tty")) {
|
|
|
|
fd = open(rpc_filename, O_RDWR);
|
|
|
|
if (fd == -1)
|
|
|
|
err(1, "Opening %s", rpc_filename);
|
|
|
|
/* Technically this is a leak, but there's only one */
|
|
|
|
notleak(io_new_conn(ld, fd, jcon_connected, ld));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
if (fd < 0) {
|
|
|
|
errx(1, "domain socket creation failed");
|
|
|
|
}
|
|
|
|
if (strlen(rpc_filename) + 1 > sizeof(addr.sun_path))
|
|
|
|
errx(1, "rpc filename '%s' too long", rpc_filename);
|
|
|
|
strcpy(addr.sun_path, rpc_filename);
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
|
|
|
|
/* Of course, this is racy! */
|
|
|
|
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0)
|
|
|
|
errx(1, "rpc filename '%s' in use", rpc_filename);
|
|
|
|
unlink(rpc_filename);
|
|
|
|
|
|
|
|
/* This file is only rw by us! */
|
|
|
|
old_umask = umask(0177);
|
|
|
|
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
|
|
|
|
err(1, "Binding rpc socket to '%s'", rpc_filename);
|
|
|
|
umask(old_umask);
|
|
|
|
|
|
|
|
if (listen(fd, 1) != 0)
|
|
|
|
err(1, "Listening on '%s'", rpc_filename);
|
|
|
|
|
gossipd: rewrite to do the handshake internally.
Now the flow is much simpler from a lightningd POV:
1. If we want to connect to a peer, just send gossipd `gossipctl_reach_peer`.
2. Every new peer, gossipd hands up to lightningd, with global/local features
and the peer fd and a gossip fd using `gossip_peer_connected`
3. If lightningd doesn't want it, it just hands the peerfd and global/local
features back to gossipd using `gossipctl_handle_peer`
4. If a peer sends a non-gossip msg (eg `open_channel`) the gossipd sends
it up using `gossip_peer_nongossip`.
5. If lightningd wants to fund a channel, it simply calls `release_channel`.
Notes:
* There's no more "unique_id": we use the peer id.
* For the moment, we don't ask gossipd when we're told to list peers, so
connected peers without a channel don't appear in the JSON getpeers API.
* We add a `gossipctl_peer_addrhint` for the moment, so you can connect to
a specific ip/port, but using other sources is a TODO.
* We now (correctly) only give up on reaching a peer after we exchange init
messages, which changes the test_disconnect case.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
7 years ago
|
|
|
log_debug(ld->log, "Listening on '%s'", rpc_filename);
|
|
|
|
/* Technically this is a leak, but there's only one */
|
|
|
|
notleak(io_new_listener(ld, fd, incoming_jcon_connected, ld));
|
|
|
|
}
|