mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
401f1debc5
We have them split over common/param.c, common/json.c, common/json_helpers.c, common/json_tok.c and common/json_stream.c. Change that to: * common/json_parse (all the json_to_xxx routines) * common/json_parse_simple (simplest the json parsing routines, for cli too) * common/json_stream (all the json_add_xxx routines) * common/json_param (all the param and param_xxx routines) Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
641 lines
18 KiB
C
641 lines
18 KiB
C
#include "config.h"
|
|
#include <arpa/inet.h>
|
|
#include <bitcoin/preimage.h>
|
|
#include <bitcoin/privkey.h>
|
|
#include <bitcoin/psbt.h>
|
|
#include <bitcoin/short_channel_id.h>
|
|
#include <bitcoin/signature.h>
|
|
#include <bitcoin/tx.h>
|
|
#include <ccan/io/io.h>
|
|
/* To reach into io_plan: not a public header! */
|
|
#include <ccan/io/backend.h>
|
|
#include <ccan/json_escape/json_escape.h>
|
|
#include <ccan/json_out/json_out.h>
|
|
#include <ccan/str/hex/hex.h>
|
|
#include <common/channel_id.h>
|
|
#include <common/configdir.h>
|
|
#include <common/json_parse.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/node_id.h>
|
|
#include <common/type_to_string.h>
|
|
#include <common/utils.h>
|
|
#include <common/wireaddr.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <wire/peer_wire.h>
|
|
|
|
static void adjust_io_write(struct json_out *jout,
|
|
ptrdiff_t delta,
|
|
struct json_stream *js)
|
|
{
|
|
/* If io_write is in progress, we shift it to point to new buffer pos */
|
|
if (js->reader)
|
|
/* FIXME: This, or something prettier (io_replan?) belong in ccan/io! */
|
|
js->reader->plan[IO_OUT].arg.u1.cp += delta;
|
|
}
|
|
|
|
struct json_stream *new_json_stream(const tal_t *ctx,
|
|
struct command *writer,
|
|
struct log *log)
|
|
{
|
|
struct json_stream *js = tal(ctx, struct json_stream);
|
|
|
|
/* FIXME: Add magic so tal_resize can fail! */
|
|
js->jout = json_out_new(js);
|
|
json_out_call_on_move(js->jout, adjust_io_write, js);
|
|
js->writer = writer;
|
|
js->reader = NULL;
|
|
js->log = log;
|
|
return js;
|
|
}
|
|
|
|
struct json_stream *json_stream_dup(const tal_t *ctx,
|
|
struct json_stream *original,
|
|
struct log *log)
|
|
{
|
|
struct json_stream *js = tal_dup(ctx, struct json_stream, original);
|
|
|
|
js->jout = json_out_dup(js, original->jout);
|
|
js->log = log;
|
|
return js;
|
|
}
|
|
|
|
/**
|
|
* json_stream_still_writing - is someone currently writing to this stream?
|
|
* @js: the json_stream.
|
|
*
|
|
* Has this json_stream not been closed yet?
|
|
*/
|
|
static bool json_stream_still_writing(const struct json_stream *js)
|
|
{
|
|
return js->writer != NULL;
|
|
}
|
|
|
|
void json_stream_log_suppress(struct json_stream *js, const char *cmd_name)
|
|
{
|
|
/* Really shouldn't be used for anything else */
|
|
assert(streq(cmd_name, "getlog"));
|
|
js->log = NULL;
|
|
}
|
|
|
|
void json_stream_append(struct json_stream *js,
|
|
const char *str, size_t len)
|
|
{
|
|
char *dest;
|
|
|
|
dest = json_out_direct(js->jout, len);
|
|
memcpy(dest, str, len);
|
|
}
|
|
|
|
/* We promise it will end in '\n\n' */
|
|
void json_stream_double_cr(struct json_stream *js)
|
|
{
|
|
const char *contents;
|
|
size_t len, cr_needed;
|
|
|
|
/* Must be well-formed at this point! */
|
|
json_out_finished(js->jout);
|
|
|
|
contents = json_out_contents(js->jout, &len);
|
|
/* It's an object (with an id!): definitely can't be less that "{}" */
|
|
assert(len >= 2);
|
|
if (contents[len-1] == '\n') {
|
|
if (contents[len-2] == '\n')
|
|
return;
|
|
cr_needed = 1;
|
|
} else
|
|
cr_needed = 2;
|
|
|
|
json_stream_append(js, "\n\n", cr_needed);
|
|
}
|
|
|
|
void json_stream_close(struct json_stream *js, struct command *writer)
|
|
{
|
|
/* FIXME: We use writer == NULL for malformed: make writer a void *?
|
|
* I used to assert(writer); here. */
|
|
assert(js->writer == writer);
|
|
|
|
/* Should be well-formed at this point! */
|
|
json_stream_double_cr(js);
|
|
json_stream_flush(js);
|
|
js->writer = NULL;
|
|
}
|
|
|
|
/* Also called when we're oom, so it will kill reader. */
|
|
void json_stream_flush(struct json_stream *js)
|
|
{
|
|
/* Wake the stream reader. FIXME: Could have a flag here to optimize */
|
|
io_wake(js);
|
|
}
|
|
|
|
char *json_member_direct(struct json_stream *js,
|
|
const char *fieldname, size_t extra)
|
|
{
|
|
char *dest;
|
|
|
|
dest = json_out_member_direct(js->jout, fieldname, extra);
|
|
return dest;
|
|
}
|
|
|
|
void json_array_start(struct json_stream *js, const char *fieldname)
|
|
{
|
|
json_out_start(js->jout, fieldname, '[');
|
|
}
|
|
|
|
void json_array_end(struct json_stream *js)
|
|
{
|
|
json_out_end(js->jout, ']');
|
|
}
|
|
|
|
void json_object_start(struct json_stream *js, const char *fieldname)
|
|
{
|
|
json_out_start(js->jout, fieldname, '{');
|
|
}
|
|
|
|
void json_object_end(struct json_stream *js)
|
|
{
|
|
json_out_end(js->jout, '}');
|
|
}
|
|
|
|
void json_add_member(struct json_stream *js,
|
|
const char *fieldname,
|
|
bool quote,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
json_out_addv(js->jout, fieldname, quote, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void json_add_jsonstr(struct json_stream *js,
|
|
const char *fieldname,
|
|
const char *jsonstr)
|
|
{
|
|
char *p;
|
|
size_t len = strlen(jsonstr);
|
|
|
|
p = json_member_direct(js, fieldname, len);
|
|
memcpy(p, jsonstr, len);
|
|
}
|
|
|
|
/* This is where we read the json_stream and write it to conn */
|
|
static struct io_plan *json_stream_output_write(struct io_conn *conn,
|
|
struct json_stream *js)
|
|
{
|
|
const char *p;
|
|
|
|
/* For when we've just done some output */
|
|
json_out_consume(js->jout, js->len_read);
|
|
|
|
/* Get how much we can write out from js */
|
|
p = json_out_contents(js->jout, &js->len_read);
|
|
|
|
/* Nothing in buffer? */
|
|
if (!p) {
|
|
/* We're not doing io_write now, unset. */
|
|
js->reader = NULL;
|
|
if (!json_stream_still_writing(js))
|
|
return js->reader_cb(conn, js, js->reader_arg);
|
|
return io_out_wait(conn, js, json_stream_output_write, js);
|
|
}
|
|
|
|
js->reader = conn;
|
|
return io_write(conn,
|
|
p, js->len_read,
|
|
json_stream_output_write, js);
|
|
}
|
|
|
|
struct io_plan *json_stream_output_(struct json_stream *js,
|
|
struct io_conn *conn,
|
|
struct io_plan *(*cb)(struct io_conn *conn,
|
|
struct json_stream *js,
|
|
void *arg),
|
|
void *arg)
|
|
{
|
|
assert(!js->reader);
|
|
|
|
js->reader_cb = cb;
|
|
js->reader_arg = arg;
|
|
|
|
js->len_read = 0;
|
|
return json_stream_output_write(conn, js);
|
|
}
|
|
|
|
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_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, len);
|
|
memcpy(dest, literal, len);
|
|
}
|
|
|
|
void json_add_stringn(struct json_stream *result, const char *fieldname,
|
|
const char *value TAKES, size_t value_len)
|
|
{
|
|
json_add_member(result, fieldname, true, "%.*s", (int)value_len, value);
|
|
if (taken(value))
|
|
tal_free(value);
|
|
}
|
|
|
|
void json_add_string(struct json_stream *result, const char *fieldname, const char *value TAKES)
|
|
{
|
|
json_add_stringn(result, fieldname, value, strlen(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);
|
|
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_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);
|
|
|
|
dest[0] = '"';
|
|
memcpy(dest + 1, esc->s, strlen(esc->s));
|
|
dest[1+strlen(esc->s)] = '"';
|
|
if (taken(esc))
|
|
tal_free(esc);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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_timeiso(struct json_stream *result,
|
|
const char *fieldname,
|
|
struct timeabs *time)
|
|
{
|
|
char iso8601_msec_fmt[sizeof("YYYY-mm-ddTHH:MM:SS.%03dZ")];
|
|
char iso8601_s[sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ")];
|
|
|
|
strftime(iso8601_msec_fmt, sizeof(iso8601_msec_fmt),
|
|
"%FT%T.%%03dZ", gmtime(&time->ts.tv_sec));
|
|
snprintf(iso8601_s, sizeof(iso8601_s),
|
|
iso8601_msec_fmt, (int) time->ts.tv_nsec / 1000000);
|
|
|
|
json_add_string(result, fieldname, iso8601_s);
|
|
}
|
|
|
|
|
|
void json_add_tok(struct json_stream *result, const char *fieldname,
|
|
const jsmntok_t *tok, const char *buffer)
|
|
{
|
|
char *space;
|
|
assert(tok->type != JSMN_UNDEFINED);
|
|
|
|
space = json_member_direct(result, fieldname, json_tok_full_len(tok));
|
|
memcpy(space, json_tok_full(buffer, tok), json_tok_full_len(tok));
|
|
}
|
|
|
|
void json_add_errcode(struct json_stream *result, const char *fieldname,
|
|
errcode_t code)
|
|
{
|
|
json_add_member(result, fieldname, false, "%"PRIerrcode, code);
|
|
}
|
|
|
|
void json_add_invstring(struct json_stream *result, const char *invstring)
|
|
{
|
|
if (strstarts(invstring, "lni"))
|
|
json_add_string(result, "bolt12", invstring);
|
|
else
|
|
json_add_string(result, "bolt11", invstring);
|
|
}
|
|
|
|
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_channel_id(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct channel_id *cid)
|
|
{
|
|
json_add_hex(response, fieldname, cid->id, sizeof(cid->id));
|
|
}
|
|
|
|
void json_add_pubkey(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct pubkey *key)
|
|
{
|
|
u8 der[PUBKEY_CMPR_LEN];
|
|
|
|
pubkey_to_der(der, key);
|
|
json_add_hex(response, fieldname, der, sizeof(der));
|
|
}
|
|
|
|
void json_add_point32(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct point32 *key)
|
|
{
|
|
u8 output[32];
|
|
|
|
secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output, &key->pubkey);
|
|
json_add_hex(response, fieldname, output, sizeof(output));
|
|
}
|
|
|
|
void json_add_bip340sig(struct json_stream *response,
|
|
const char *fieldname,
|
|
const struct bip340sig *sig)
|
|
{
|
|
json_add_hex(response, fieldname, sig->u8, sizeof(sig->u8));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void json_add_outpoint(struct json_stream *result, const char *fieldname,
|
|
const struct bitcoin_outpoint *out)
|
|
{
|
|
char hex[hex_str_size(sizeof(out->txid))];
|
|
bitcoin_txid_to_hex(&out->txid, hex, sizeof(hex));
|
|
json_add_member(result, fieldname, true, "%s:%d", hex, out->n);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
void json_add_address(struct json_stream *response, const char *fieldname,
|
|
const struct wireaddr *addr)
|
|
{
|
|
json_object_start(response, fieldname);
|
|
if (addr->type == ADDR_TYPE_IPV4) {
|
|
char addrstr[INET_ADDRSTRLEN];
|
|
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) {
|
|
char addrstr[INET6_ADDRSTRLEN];
|
|
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_REMOVED) {
|
|
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);
|
|
} else if (addr->type == ADDR_TYPE_DNS) {
|
|
json_add_string(response, "type", "dns");
|
|
json_add_string(response, "address", fmt_wireaddr_without_port(tmpctx, addr));
|
|
json_add_num(response, "port", addr->port);
|
|
} else if (addr->type == ADDR_TYPE_WEBSOCKET) {
|
|
json_add_string(response, "type", "websocket");
|
|
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.address);
|
|
json_object_end(response);
|
|
return;
|
|
case ADDR_INTERNAL_STATICTOR:
|
|
json_object_start(response, fieldname);
|
|
json_add_string(response, "type", "Tor from blob generated static address");
|
|
json_add_address(response, "service", &addr->u.torservice.address);
|
|
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_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_psbt(struct json_stream *stream,
|
|
const char *fieldname,
|
|
const struct wally_psbt *psbt TAKES)
|
|
{
|
|
const char *psbt_b64;
|
|
psbt_b64 = psbt_to_b64(NULL, psbt);
|
|
json_add_string(stream, fieldname, take(psbt_b64));
|
|
if (taken(psbt))
|
|
tal_free(psbt);
|
|
}
|
|
|
|
void json_add_amount_msat_compat(struct json_stream *result,
|
|
struct amount_msat msat,
|
|
const char *rawfieldname,
|
|
const char *msatfieldname)
|
|
{
|
|
if (deprecated_apis)
|
|
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)
|
|
{
|
|
if (!deprecated_apis)
|
|
assert(strends(msatfieldname, "_msat"));
|
|
if (deprecated_apis)
|
|
json_add_string(result, msatfieldname,
|
|
type_to_string(tmpctx, struct amount_msat, &msat));
|
|
else
|
|
json_add_u64(result, msatfieldname, msat.millisatoshis); /* Raw: low-level helper */
|
|
}
|
|
|
|
void json_add_amount_sat_compat(struct json_stream *result,
|
|
struct amount_sat sat,
|
|
const char *rawfieldname,
|
|
const char *msatfieldname)
|
|
{
|
|
if (deprecated_apis)
|
|
json_add_u64(result, rawfieldname, sat.satoshis); /* Raw: low-level helper */
|
|
json_add_amount_sat_msat(result, msatfieldname, sat);
|
|
}
|
|
|
|
void json_add_amount_sat_msat(struct json_stream *result,
|
|
const char *msatfieldname,
|
|
struct amount_sat sat)
|
|
{
|
|
struct amount_msat msat;
|
|
assert(strends(msatfieldname, "_msat"));
|
|
if (amount_sat_to_msat(&msat, sat))
|
|
json_add_amount_msat_only(result, msatfieldname, msat);
|
|
}
|
|
|
|
/* When I noticed that we were adding "XXXmsat" fields *not* ending in _msat */
|
|
void json_add_amount_sats_deprecated(struct json_stream *result,
|
|
const char *fieldname,
|
|
const char *msatfieldname,
|
|
struct amount_sat sat)
|
|
{
|
|
if (deprecated_apis) {
|
|
struct amount_msat msat;
|
|
assert(!strends(fieldname, "_msat"));
|
|
if (amount_sat_to_msat(&msat, sat))
|
|
json_add_string(result, fieldname,
|
|
take(fmt_amount_msat(NULL, msat)));
|
|
}
|
|
json_add_amount_sat_msat(result, msatfieldname, sat);
|
|
}
|
|
|
|
void json_add_sats(struct json_stream *result,
|
|
const char *fieldname,
|
|
struct amount_sat sat)
|
|
{
|
|
json_add_string(result, fieldname, take(fmt_amount_sat(NULL, sat)));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
void json_add_preimage(struct json_stream *result, const char *fieldname,
|
|
const struct preimage *preimage)
|
|
{
|
|
json_add_hex(result, fieldname, preimage, sizeof(*preimage));
|
|
}
|
|
|
|
void json_add_lease_rates(struct json_stream *result,
|
|
const struct lease_rates *rates)
|
|
{
|
|
json_add_amount_sat_msat(result, "lease_fee_base_msat",
|
|
amount_sat(rates->lease_fee_base_sat));
|
|
json_add_num(result, "lease_fee_basis", rates->lease_fee_basis);
|
|
json_add_num(result, "funding_weight", rates->funding_weight);
|
|
json_add_amount_msat_only(result,
|
|
"channel_fee_max_base_msat",
|
|
amount_msat(rates->channel_fee_max_base_msat));
|
|
json_add_num(result, "channel_fee_max_proportional_thousandths",
|
|
rates->channel_fee_max_proportional_thousandths);
|
|
}
|
|
|