|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ccan/mem/mem.h>
|
|
|
|
#include <ccan/str/hex/hex.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
#include <common/json.h>
|
|
|
|
#include <common/json_command.h>
|
|
|
|
#include <common/json_escaped.h>
|
|
|
|
#include <common/json_helpers.h>
|
|
|
|
#include <common/jsonrpc_errors.h>
|
|
|
|
#include <common/memleak.h>
|
common/node_id: new type.
Node ids are pubkeys, but we only use them as pubkeys for routing and checking
gossip messages. So we're packing and unpacking them constantly, and wasting
some space and time.
This introduces a new type, explicitly the SEC1 compressed encoding
(33 bytes). We ensure its validity when we load from the db, or get it
from JSON. We still use 'struct pubkey' for peer messages, which checks
validity.
Results from 5 runs, min-max(mean +/- stddev):
store_load_msec,vsz_kb,store_rewrite_sec,listnodes_sec,listchannels_sec,routing_sec,peer_write_all_sec
39475-39572(39518+/-36),2880732,41.150000-41.390000(41.298+/-0.085),2.260000-2.550000(2.336+/-0.11),44.390000-65.150000(58.648+/-7.5),32.740000-33.020000(32.89+/-0.093),44.130000-45.090000(44.566+/-0.32)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
6 years ago
|
|
|
#include <common/node_id.h>
|
|
|
|
#include <common/param.h>
|
|
|
|
#include <common/type_to_string.h>
|
|
|
|
#include <common/wallet_tx.h>
|
|
|
|
#include <common/wireaddr.h>
|
|
|
|
#include <gossipd/routing.h>
|
|
|
|
#include <lightningd/json.h>
|
|
|
|
#include <lightningd/json_stream.h>
|
|
|
|
#include <lightningd/jsonrpc.h>
|
|
|
|
#include <lightningd/options.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <wallet/wallet.h>
|
|
|
|
#include <wire/wire.h>
|
|
|
|
|
|
|
|
/* Output a route hop */
|
|
|
|
static void
|
|
|
|
json_add_route_hop(struct json_stream *r, char const *n,
|
|
|
|
const struct route_hop *h)
|
|
|
|
{
|
|
|
|
/* Imitate what getroute/sendpay use */
|
|
|
|
json_object_start(r, n);
|
|
|
|
json_add_node_id(r, "id", &h->nodeid);
|
|
|
|
json_add_short_channel_id(r, "channel",
|
|
|
|
&h->channel_id);
|
|
|
|
json_add_num(r, "direction", h->direction);
|
|
|
|
json_add_amount_msat_compat(r, h->amount, "msatoshi", "amount_msat");
|
|
|
|
json_add_num(r, "delay", h->delay);
|
|
|
|
json_object_end(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output a route */
|
|
|
|
void
|
|
|
|
json_add_route(struct json_stream *r, char const *n,
|
|
|
|
const struct route_hop *hops, size_t hops_len)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
json_array_start(r, n);
|
|
|
|
for (i = 0; i < hops_len; ++i) {
|
|
|
|
json_add_route_hop(r, NULL, &hops[i]);
|
|
|
|
}
|
|
|
|
json_array_end(r);
|
|
|
|
}
|
|
|
|
|
common/node_id: new type.
Node ids are pubkeys, but we only use them as pubkeys for routing and checking
gossip messages. So we're packing and unpacking them constantly, and wasting
some space and time.
This introduces a new type, explicitly the SEC1 compressed encoding
(33 bytes). We ensure its validity when we load from the db, or get it
from JSON. We still use 'struct pubkey' for peer messages, which checks
validity.
Results from 5 runs, min-max(mean +/- stddev):
store_load_msec,vsz_kb,store_rewrite_sec,listnodes_sec,listchannels_sec,routing_sec,peer_write_all_sec
39475-39572(39518+/-36),2880732,41.150000-41.390000(41.298+/-0.085),2.260000-2.550000(2.336+/-0.11),44.390000-65.150000(58.648+/-7.5),32.740000-33.020000(32.89+/-0.093),44.130000-45.090000(44.566+/-0.32)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
6 years ago
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_pubkey(struct json_stream *response,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct pubkey *key)
|
|
|
|
{
|
pubkey: rename PUBKEY_DER_LEN to PUBKEY_CMPR_LEN.
Pubkeys are not not actually DER encoding, but Pieter Wuille corrected
me: it's SEC 1 documented encoding.
Results from 5 runs, min-max(mean +/- stddev):
store_load_msec,vsz_kb,store_rewrite_sec,listnodes_sec,listchannels_sec,routing_sec,peer_write_all_sec
38922-39297(39180.6+/-1.3e+02),2880728,41.040000-41.160000(41.106+/-0.05),2.270000-2.530000(2.338+/-0.097),44.570000-53.980000(49.696+/-3),32.840000-33.080000(32.95+/-0.095),43.060000-44.950000(43.696+/-0.72)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
6 years ago
|
|
|
u8 der[PUBKEY_CMPR_LEN];
|
|
|
|
|
|
|
|
pubkey_to_der(der, key);
|
|
|
|
json_add_hex(response, fieldname, der, sizeof(der));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_txid(struct json_stream *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);
|
|
|
|
}
|
|
|
|
|
common/node_id: new type.
Node ids are pubkeys, but we only use them as pubkeys for routing and checking
gossip messages. So we're packing and unpacking them constantly, and wasting
some space and time.
This introduces a new type, explicitly the SEC1 compressed encoding
(33 bytes). We ensure its validity when we load from the db, or get it
from JSON. We still use 'struct pubkey' for peer messages, which checks
validity.
Results from 5 runs, min-max(mean +/- stddev):
store_load_msec,vsz_kb,store_rewrite_sec,listnodes_sec,listchannels_sec,routing_sec,peer_write_all_sec
39475-39572(39518+/-36),2880732,41.150000-41.390000(41.298+/-0.085),2.260000-2.550000(2.336+/-0.11),44.390000-65.150000(58.648+/-7.5),32.740000-33.020000(32.89+/-0.093),44.130000-45.090000(44.566+/-0.32)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
6 years ago
|
|
|
struct command_result *param_node_id(struct command *cmd,
|
|
|
|
const char *name,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *tok,
|
|
|
|
struct node_id **id)
|
|
|
|
{
|
|
|
|
*id = tal(cmd, struct node_id);
|
|
|
|
if (json_to_node_id(buffer, tok, *id))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' should be a pubkey, not '%.*s'",
|
|
|
|
name, json_tok_full_len(tok),
|
|
|
|
json_tok_full(buffer, tok));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct command_result *param_pubkey(struct command *cmd, const char *name,
|
|
|
|
const char *buffer, const jsmntok_t *tok,
|
|
|
|
struct pubkey **pubkey)
|
|
|
|
{
|
|
|
|
*pubkey = tal(cmd, struct pubkey);
|
|
|
|
if (json_to_pubkey(buffer, tok, *pubkey))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' should be a pubkey, not '%.*s'",
|
|
|
|
name, json_tok_full_len(tok),
|
|
|
|
json_tok_full(buffer, tok));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_short_channel_id(struct json_stream *response,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct short_channel_id *scid)
|
|
|
|
{
|
|
|
|
json_add_member(response, fieldname, "\"%dx%dx%d\"",
|
|
|
|
short_channel_id_blocknum(scid),
|
|
|
|
short_channel_id_txnum(scid),
|
|
|
|
short_channel_id_outnum(scid));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct command_result *param_short_channel_id(struct command *cmd,
|
|
|
|
const char *name,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *tok,
|
|
|
|
struct short_channel_id **scid)
|
|
|
|
{
|
|
|
|
*scid = tal(cmd, struct short_channel_id);
|
|
|
|
if (json_to_short_channel_id(buffer, tok, *scid,
|
|
|
|
deprecated_apis))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' should be a short channel id, not '%.*s'",
|
|
|
|
name, json_tok_full_len(tok),
|
|
|
|
json_tok_full(buffer, tok));
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *json_feerate_style_name(enum feerate_style style)
|
|
|
|
{
|
|
|
|
switch (style) {
|
|
|
|
case FEERATE_PER_KBYTE:
|
|
|
|
return "perkb";
|
|
|
|
case FEERATE_PER_KSIPA:
|
|
|
|
return "perkw";
|
|
|
|
}
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
struct command_result *param_feerate_style(struct command *cmd,
|
|
|
|
const char *name,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *tok,
|
|
|
|
enum feerate_style **style)
|
|
|
|
{
|
|
|
|
*style = tal(cmd, enum feerate_style);
|
|
|
|
if (json_tok_streq(buffer, tok,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KSIPA))) {
|
|
|
|
**style = FEERATE_PER_KSIPA;
|
|
|
|
return NULL;
|
|
|
|
} else if (json_tok_streq(buffer, tok,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KBYTE))) {
|
|
|
|
**style = FEERATE_PER_KBYTE;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' should be '%s' or '%s', not '%.*s'",
|
|
|
|
name,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KSIPA),
|
|
|
|
json_feerate_style_name(FEERATE_PER_KBYTE),
|
|
|
|
json_tok_full_len(tok), json_tok_full(buffer, tok));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct command_result *param_feerate(struct command *cmd, const char *name,
|
|
|
|
const char *buffer, const jsmntok_t *tok,
|
|
|
|
u32 **feerate)
|
|
|
|
{
|
|
|
|
jsmntok_t base = *tok, suffix = *tok;
|
|
|
|
enum feerate_style style;
|
|
|
|
unsigned int num;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < NUM_FEERATES; i++) {
|
|
|
|
if (json_tok_streq(buffer, tok, feerate_name(i)))
|
|
|
|
return param_feerate_estimate(cmd, feerate, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We have to split the number and suffix. */
|
|
|
|
suffix.start = suffix.end;
|
|
|
|
while (suffix.start > base.start && !isdigit(buffer[suffix.start-1])) {
|
|
|
|
suffix.start--;
|
|
|
|
base.end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!json_to_number(buffer, &base, &num)) {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' prefix should be an integer, not '%.*s'",
|
|
|
|
name, base.end - base.start,
|
|
|
|
buffer + base.start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (json_tok_streq(buffer, &suffix, "")
|
|
|
|
|| json_tok_streq(buffer, &suffix,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KBYTE))) {
|
|
|
|
style = FEERATE_PER_KBYTE;
|
|
|
|
} else if (json_tok_streq(buffer, &suffix,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KSIPA))) {
|
|
|
|
style = FEERATE_PER_KSIPA;
|
|
|
|
} else {
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' suffix should be '%s' or '%s', not '%.*s'",
|
|
|
|
name,
|
|
|
|
json_feerate_style_name(FEERATE_PER_KSIPA),
|
|
|
|
json_feerate_style_name(FEERATE_PER_KBYTE),
|
|
|
|
suffix.end - suffix.start,
|
|
|
|
buffer + suffix.start);
|
|
|
|
}
|
|
|
|
|
|
|
|
*feerate = tal(cmd, u32);
|
|
|
|
**feerate = feerate_from_style(num, style);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
json_tok_channel_id(const char *buffer, const jsmntok_t *tok,
|
|
|
|
struct channel_id *cid)
|
|
|
|
{
|
|
|
|
return hex_decode(buffer + tok->start, tok->end - tok->start,
|
|
|
|
cid, sizeof(*cid));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_address(struct json_stream *response, const char *fieldname,
|
|
|
|
const struct wireaddr *addr)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
} else if (addr->type == ADDR_TYPE_TOR_V2) {
|
|
|
|
json_add_string(response, "type", "torv2");
|
|
|
|
json_add_string(response, "address", fmt_wireaddr_without_port(tmpctx, addr));
|
|
|
|
json_add_num(response, "port", addr->port);
|
|
|
|
} else if (addr->type == ADDR_TYPE_TOR_V3) {
|
|
|
|
json_add_string(response, "type", "torv3");
|
|
|
|
json_add_string(response, "address", fmt_wireaddr_without_port(tmpctx, addr));
|
|
|
|
json_add_num(response, "port", addr->port);
|
|
|
|
}
|
|
|
|
json_object_end(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_address_internal(struct json_stream *response,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct wireaddr_internal *addr)
|
|
|
|
{
|
|
|
|
switch (addr->itype) {
|
|
|
|
case ADDR_INTERNAL_SOCKNAME:
|
|
|
|
json_object_start(response, fieldname);
|
|
|
|
json_add_string(response, "type", "local socket");
|
|
|
|
json_add_string(response, "socket", addr->u.sockname);
|
|
|
|
json_object_end(response);
|
|
|
|
return;
|
|
|
|
case ADDR_INTERNAL_ALLPROTO:
|
|
|
|
json_object_start(response, fieldname);
|
|
|
|
json_add_string(response, "type", "any protocol");
|
|
|
|
json_add_num(response, "port", addr->u.port);
|
|
|
|
json_object_end(response);
|
|
|
|
return;
|
|
|
|
case ADDR_INTERNAL_AUTOTOR:
|
|
|
|
json_object_start(response, fieldname);
|
|
|
|
json_add_string(response, "type", "Tor generated address");
|
|
|
|
json_add_address(response, "service", &addr->u.torservice);
|
|
|
|
json_object_end(response);
|
|
|
|
return;
|
|
|
|
case ADDR_INTERNAL_FORPROXY:
|
|
|
|
json_object_start(response, fieldname);
|
|
|
|
json_add_string(response, "type", "unresolved");
|
|
|
|
json_add_string(response, "name", addr->u.unresolved.name);
|
|
|
|
json_add_num(response, "port", addr->u.unresolved.port);
|
|
|
|
json_object_end(response);
|
|
|
|
return;
|
|
|
|
case ADDR_INTERNAL_WIREADDR:
|
|
|
|
json_add_address(response, fieldname, &addr->u.wireaddr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_num(struct json_stream *result, const char *fieldname, unsigned int value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%u", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_double(struct json_stream *result, const char *fieldname, double value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%f", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_u64(struct json_stream *result, const char *fieldname,
|
|
|
|
uint64_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%"PRIu64, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_s64(struct json_stream *result, const char *fieldname,
|
|
|
|
int64_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%"PRIi64, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_u32(struct json_stream *result, const char *fieldname,
|
|
|
|
uint32_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%d", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_s32(struct json_stream *result, const char *fieldname,
|
|
|
|
int32_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%d", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_literal(struct json_stream *result, const char *fieldname,
|
|
|
|
const char *literal, int len)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%.*s", len, literal);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_string(struct json_stream *result, const char *fieldname, const char *value TAKES)
|
|
|
|
{
|
|
|
|
struct json_escaped *esc = json_partial_escape(NULL, value);
|
|
|
|
|
|
|
|
json_add_member(result, fieldname, "\"%s\"", esc->s);
|
|
|
|
tal_free(esc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_bool(struct json_stream *result, const char *fieldname, bool value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, value ? "true" : "false");
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_null(struct json_stream *stream, const char *fieldname)
|
|
|
|
{
|
|
|
|
json_add_member(stream, fieldname, "null");
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_hex_talarr(struct json_stream *result,
|
|
|
|
const char *fieldname,
|
|
|
|
const tal_t *data)
|
|
|
|
{
|
|
|
|
json_add_hex(result, fieldname, data, tal_bytelen(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_escaped_string(struct json_stream *result, const char *fieldname,
|
|
|
|
const struct json_escaped *esc TAKES)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "\"%s\"", esc->s);
|
|
|
|
if (taken(esc))
|
|
|
|
tal_free(esc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_amount_msat_compat(struct json_stream *result,
|
|
|
|
struct amount_msat msat,
|
|
|
|
const char *rawfieldname,
|
|
|
|
const char *msatfieldname)
|
|
|
|
{
|
|
|
|
json_add_u64(result, rawfieldname, msat.millisatoshis); /* Raw: low-level helper */
|
|
|
|
json_add_amount_msat_only(result, msatfieldname, msat);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_amount_msat_only(struct json_stream *result,
|
|
|
|
const char *msatfieldname,
|
|
|
|
struct amount_msat msat)
|
|
|
|
{
|
|
|
|
json_add_member(result, msatfieldname, "\"%s\"",
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &msat));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_amount_sat_compat(struct json_stream *result,
|
|
|
|
struct amount_sat sat,
|
|
|
|
const char *rawfieldname,
|
|
|
|
const char *msatfieldname)
|
|
|
|
{
|
|
|
|
json_add_u64(result, rawfieldname, sat.satoshis); /* Raw: low-level helper */
|
|
|
|
json_add_amount_sat_only(result, msatfieldname, sat);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_amount_sat_only(struct json_stream *result,
|
|
|
|
const char *msatfieldname,
|
|
|
|
struct amount_sat sat)
|
|
|
|
{
|
|
|
|
struct amount_msat msat;
|
|
|
|
if (amount_sat_to_msat(&msat, sat))
|
|
|
|
json_add_member(result, msatfieldname, "\"%s\"",
|
|
|
|
type_to_string(tmpctx, struct amount_msat, &msat));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_timeabs(struct json_stream *result, const char *fieldname,
|
|
|
|
struct timeabs t)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, "%" PRIu64 ".%03" PRIu64,
|
|
|
|
(u64)t.ts.tv_sec, (u64)t.ts.tv_nsec / 1000000);
|
|
|
|
}
|