|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ccan/json_escape/json_escape.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_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct command_result *param_txid(struct command *cmd,
|
|
|
|
const char *name,
|
|
|
|
const char *buffer,
|
|
|
|
const jsmntok_t *tok,
|
|
|
|
struct bitcoin_txid **txid)
|
|
|
|
{
|
|
|
|
*txid = tal(cmd, struct bitcoin_txid);
|
|
|
|
if (json_to_txid(buffer, tok, *txid))
|
|
|
|
return NULL;
|
|
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"'%s' should be txid, 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, true, "%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))
|
|
|
|
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, false, "%u", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_double(struct json_stream *result, const char *fieldname, double value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, "%f", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_u64(struct json_stream *result, const char *fieldname,
|
|
|
|
uint64_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, "%"PRIu64, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_s64(struct json_stream *result, const char *fieldname,
|
|
|
|
int64_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, "%"PRIi64, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_u32(struct json_stream *result, const char *fieldname,
|
|
|
|
uint32_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, "%u", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_s32(struct json_stream *result, const char *fieldname,
|
|
|
|
int32_t value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, "%d", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_literal(struct json_stream *result, const char *fieldname,
|
|
|
|
const char *literal, int len)
|
|
|
|
{
|
|
|
|
/* Literal may contain quotes, so bypass normal checks */
|
|
|
|
char *dest = json_member_direct(result, fieldname, strlen(literal));
|
|
|
|
if (dest)
|
|
|
|
memcpy(dest, literal, strlen(literal));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_string(struct json_stream *result, const char *fieldname, const char *value TAKES)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, true, "%s", value);
|
|
|
|
if (taken(value))
|
|
|
|
tal_free(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_bool(struct json_stream *result, const char *fieldname, bool value)
|
|
|
|
{
|
|
|
|
json_add_member(result, fieldname, false, value ? "true" : "false");
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_null(struct json_stream *stream, const char *fieldname)
|
|
|
|
{
|
|
|
|
json_add_member(stream, fieldname, false, "null");
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_hex(struct json_stream *js, const char *fieldname,
|
|
|
|
const void *data, size_t len)
|
|
|
|
{
|
|
|
|
/* Size without NUL term */
|
|
|
|
size_t hexlen = hex_str_size(len) - 1;
|
|
|
|
char *dest;
|
|
|
|
|
|
|
|
dest = json_member_direct(js, fieldname, 1 + hexlen + 1);
|
|
|
|
if (dest) {
|
|
|
|
dest[0] = '"';
|
|
|
|
if (!hex_encode(data, len, dest + 1, hexlen + 1))
|
|
|
|
abort();
|
|
|
|
dest[1+hexlen] = '"';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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_tx(struct json_stream *result,
|
|
|
|
const char *fieldname,
|
|
|
|
const struct bitcoin_tx *tx)
|
|
|
|
{
|
|
|
|
json_add_hex_talarr(result, fieldname, linearize_tx(tmpctx, tx));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_escaped_string(struct json_stream *result, const char *fieldname,
|
|
|
|
const struct json_escape *esc TAKES)
|
|
|
|
{
|
|
|
|
/* Already escaped, don't re-escape! */
|
|
|
|
char *dest = json_member_direct(result, fieldname,
|
|
|
|
1 + strlen(esc->s) + 1);
|
|
|
|
|
|
|
|
if (dest) {
|
|
|
|
dest[0] = '"';
|
|
|
|
memcpy(dest + 1, esc->s, strlen(esc->s));
|
|
|
|
dest[1+strlen(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_string(result, msatfieldname,
|
|
|
|
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_string(result, msatfieldname,
|
|
|
|
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, false, "%" PRIu64 ".%03" PRIu64,
|
|
|
|
(u64)t.ts.tv_sec, (u64)t.ts.tv_nsec / 1000000);
|
|
|
|
}
|
plugin: Add new notification type: warning
This notification bases on `LOG_BROKEN` and `LOG_UNUSUAL` level log.
--Introduction
A notification for topic `warning` is sent every time a new `BROKEN`/
`UNUSUAL` level(in plugins, we use `error`/`warn`) log generated, which
means an unusual/borken thing happens, such as channel failed,
message resolving failed...
```json
{
"warning": {
"level": "warn",
"time": "1559743608.565342521",
"source": "lightningd(17652): 0821f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c chan #7854:",
"log": "Peer permanent failure in CHANNELD_NORMAL: lightning_channeld: sent ERROR bad reestablish dataloss msg"
}
}
```
1. `level` is `warn` or `error`:
`warn` means something seems bad happened and it's under control, but
we'd better check it;
`error` means something extremely bad is out of control, and it may lead
to crash;
2. `time` is the second since epoch;
3. `source`, in fact, is the `prefix` of the log_entry. It means where
the event happened, it may have the following forms:
`<node_id> chan #<db_id_of_channel>:`, `lightningd(<lightningd_pid>):`,
`plugin-<plugin_name>:`, `<daemon_name>(<daemon_pid>):`, `jsonrpc:`,
`jcon fd <error_fd_to_jsonrpc>:`, `plugin-manager`;
4. `log` is the context of the original log entry.
--Note:
1. The main code uses `UNUSUAL`/`BROKEN`, and plugin module uses `warn`
/`error`, considering the consistency with plugin, warning choose `warn`
/`error`. But users who use c-lightning with plugins may want to
`getlog` with specified level when receive warning. It's the duty for
plugin dev to turn `warn`/`error` into `UNUSUAL`/`BROKEN` and present it
to the users, or pass it directly to `getlog`;
2. About time, `json_log()` in `log` module uses the Relative Time, from
the time when `log_book` inited to the time when this event happend.
But I consider the `UNUSUAL`/`BROKEN` event is rare, and it is very
likely to happen after running for a long time, so for users, they will
pay more attention to Absolute Time.
-- Related Change
1. Remove the definitions of `log`, `log_book`, `log_entry` from `log.c`
to `log.h`, then they can be used in warning declaration and definition.
2. Remove `void json_add_time(struct json_stream *result, const char
*fieldname, struct timespec ts)` from `log.c` to `json.c`, and add
related declaration in `json.h`. Now the notification function in
`notification.c` can call it.
2. Add a pointer to `struct lightningd` in `struct log_book`. This may
affect the independence of the `log` module, but storing a pointer to
`ld` is more direct;
6 years ago
|
|
|
|
|
|
|
void json_add_time(struct json_stream *result, const char *fieldname,
|
|
|
|
struct timespec ts)
|
|
|
|
{
|
|
|
|
char timebuf[100];
|
|
|
|
|
|
|
|
snprintf(timebuf, sizeof(timebuf), "%lu.%09u",
|
|
|
|
(unsigned long)ts.tv_sec,
|
|
|
|
(unsigned)ts.tv_nsec);
|
|
|
|
json_add_string(result, fieldname, timebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_secret(struct json_stream *response, const char *fieldname,
|
|
|
|
const struct secret *secret)
|
|
|
|
{
|
|
|
|
json_add_hex(response, fieldname, secret, sizeof(struct secret));
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_sha256(struct json_stream *result, const char *fieldname,
|
|
|
|
const struct sha256 *hash)
|
|
|
|
{
|
|
|
|
json_add_hex(result, fieldname, hash, sizeof(*hash));
|
|
|
|
}
|