2017-08-28 18:04:01 +02:00
|
|
|
#include <bitcoin/chainparams.h>
|
2017-10-23 07:05:28 +02:00
|
|
|
#include <ccan/array_size/array_size.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <ccan/err/err.h>
|
2018-01-29 01:30:15 +01:00
|
|
|
#include <ccan/mem/mem.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <ccan/opt/opt.h>
|
2018-01-29 01:30:15 +01:00
|
|
|
#include <ccan/opt/private.h>
|
2017-10-22 15:32:16 +02:00
|
|
|
#include <ccan/read_write_all/read_write_all.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <ccan/short_types/short_types.h>
|
|
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
2018-02-16 03:00:41 +01:00
|
|
|
#include <ccan/tal/path/path.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <ccan/tal/str/str.h>
|
2017-08-28 18:04:01 +02:00
|
|
|
#include <common/configdir.h>
|
2017-12-15 11:22:57 +01:00
|
|
|
#include <common/memleak.h>
|
2017-08-28 18:02:01 +02:00
|
|
|
#include <common/version.h>
|
2017-10-23 06:17:38 +02:00
|
|
|
#include <common/wireaddr.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <errno.h>
|
2017-10-22 15:32:16 +02:00
|
|
|
#include <fcntl.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <inttypes.h>
|
2017-08-28 18:04:01 +02:00
|
|
|
#include <lightningd/bitcoind.h>
|
|
|
|
#include <lightningd/chaintopology.h>
|
2018-08-10 17:00:34 +02:00
|
|
|
#include <lightningd/json.h>
|
2018-10-19 03:17:48 +02:00
|
|
|
#include <lightningd/json_escaped.h>
|
2018-01-29 01:30:15 +01:00
|
|
|
#include <lightningd/jsonrpc.h>
|
2018-05-24 23:40:18 +02:00
|
|
|
#include <lightningd/jsonrpc_errors.h>
|
2017-08-28 18:04:01 +02:00
|
|
|
#include <lightningd/lightningd.h>
|
|
|
|
#include <lightningd/log.h>
|
|
|
|
#include <lightningd/options.h>
|
2018-07-20 03:14:02 +02:00
|
|
|
#include <lightningd/param.h>
|
2018-09-20 13:46:50 +02:00
|
|
|
#include <lightningd/plugin.h>
|
2017-10-06 13:57:29 +02:00
|
|
|
#include <lightningd/subd.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
2017-05-08 15:15:29 +02:00
|
|
|
#include <wire/wire.h>
|
2017-01-04 03:52:29 +01:00
|
|
|
|
2018-01-16 20:43:14 +01:00
|
|
|
bool deprecated_apis = true;
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Tal wrappers for opt. */
|
|
|
|
static void *opt_allocfn(size_t size)
|
|
|
|
{
|
2018-07-28 08:00:20 +02:00
|
|
|
return tal_arr_label(NULL, char, size, TAL_LABEL("opt_allocfn", ""));
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void *tal_reallocfn(void *ptr, size_t size)
|
|
|
|
{
|
2017-12-15 11:22:57 +01:00
|
|
|
if (!ptr) {
|
|
|
|
/* realloc(NULL) call is to allocate opt_table */
|
|
|
|
static bool opt_table_alloced = false;
|
|
|
|
if (!opt_table_alloced) {
|
|
|
|
opt_table_alloced = true;
|
|
|
|
return notleak(opt_allocfn(size));
|
|
|
|
}
|
2017-01-04 03:52:29 +01:00
|
|
|
return opt_allocfn(size);
|
2017-12-15 11:22:57 +01:00
|
|
|
}
|
2017-01-04 03:52:29 +01:00
|
|
|
tal_resize_(&ptr, 1, size, false);
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tal_freefn(void *ptr)
|
|
|
|
{
|
|
|
|
tal_free(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Put into ccan/time. */
|
|
|
|
#define TIME_FROM_SEC(sec) { { .tv_nsec = 0, .tv_sec = sec } }
|
|
|
|
#define TIME_FROM_MSEC(msec) \
|
|
|
|
{ { .tv_nsec = ((msec) % 1000) * 1000000, .tv_sec = (msec) / 1000 } }
|
|
|
|
|
2018-03-18 13:22:21 +01:00
|
|
|
static char *opt_set_u64(const char *arg, u64 *u)
|
|
|
|
{
|
|
|
|
char *endp;
|
|
|
|
unsigned long long l;
|
|
|
|
|
|
|
|
assert(arg != NULL);
|
|
|
|
|
|
|
|
/* This is how the manpage says to do it. Yech. */
|
|
|
|
errno = 0;
|
|
|
|
l = strtoull(arg, &endp, 0);
|
|
|
|
if (*endp || !arg[0])
|
|
|
|
return tal_fmt(NULL, "'%s' is not a number", arg);
|
|
|
|
*u = l;
|
|
|
|
if (errno || *u != l)
|
|
|
|
return tal_fmt(NULL, "'%s' is out of range", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-01-04 03:52:29 +01:00
|
|
|
static char *opt_set_u32(const char *arg, u32 *u)
|
|
|
|
{
|
|
|
|
char *endp;
|
|
|
|
unsigned long l;
|
|
|
|
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* This is how the manpage says to do it. Yech. */
|
|
|
|
errno = 0;
|
|
|
|
l = strtoul(arg, &endp, 0);
|
|
|
|
if (*endp || !arg[0])
|
|
|
|
return tal_fmt(NULL, "'%s' is not a number", arg);
|
|
|
|
*u = l;
|
|
|
|
if (errno || *u != l)
|
|
|
|
return tal_fmt(NULL, "'%s' is out of range", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_s32(const char *arg, s32 *u)
|
|
|
|
{
|
|
|
|
char *endp;
|
|
|
|
long l;
|
|
|
|
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* This is how the manpage says to do it. Yech. */
|
|
|
|
errno = 0;
|
|
|
|
l = strtol(arg, &endp, 0);
|
|
|
|
if (*endp || !arg[0])
|
|
|
|
return tal_fmt(NULL, "'%s' is not a number", arg);
|
|
|
|
*u = l;
|
|
|
|
if (errno || *u != l)
|
|
|
|
return tal_fmt(NULL, "'%s' is out of range", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-05-07 06:28:12 +02:00
|
|
|
static char *opt_add_addr_withtype(const char *arg,
|
|
|
|
struct lightningd *ld,
|
2018-05-10 04:39:02 +02:00
|
|
|
enum addr_listen_announce ala,
|
|
|
|
bool wildcard_ok)
|
2017-10-11 12:04:50 +02:00
|
|
|
{
|
2018-02-25 03:30:33 +01:00
|
|
|
char const *err_msg;
|
2017-10-23 06:15:38 +02:00
|
|
|
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2018-09-27 02:19:24 +02:00
|
|
|
*tal_arr_expand(&ld->proposed_listen_announce) = ala;
|
|
|
|
if (!parse_wireaddr_internal(arg, tal_arr_expand(&ld->proposed_wireaddr),
|
|
|
|
ld->portnum,
|
2018-05-10 05:44:23 +02:00
|
|
|
wildcard_ok, !ld->use_proxy_always, false,
|
2018-05-10 04:39:02 +02:00
|
|
|
&err_msg)) {
|
2018-05-07 06:29:21 +02:00
|
|
|
return tal_fmt(NULL, "Unable to parse address '%s': %s", arg, err_msg);
|
2018-01-26 18:48:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2017-10-11 12:04:50 +02:00
|
|
|
|
2017-05-08 15:15:29 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 06:28:12 +02:00
|
|
|
static char *opt_add_addr(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-05-10 04:39:02 +02:00
|
|
|
return opt_add_addr_withtype(arg, ld, ADDR_LISTEN_AND_ANNOUNCE, true);
|
2018-05-07 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_add_bind_addr(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-05-10 04:39:02 +02:00
|
|
|
return opt_add_addr_withtype(arg, ld, ADDR_LISTEN, true);
|
2018-05-07 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_add_announce_addr(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-06-24 07:16:53 +02:00
|
|
|
const struct wireaddr *wn;
|
|
|
|
size_t n = tal_count(ld->proposed_wireaddr);
|
|
|
|
char *err = opt_add_addr_withtype(arg, ld, ADDR_ANNOUNCE, false);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Can't announce anything that's not a normal wireaddr. */
|
|
|
|
if (ld->proposed_wireaddr[n].itype != ADDR_INTERNAL_WIREADDR)
|
|
|
|
return tal_fmt(NULL, "address '%s' is not announcable",
|
|
|
|
arg);
|
|
|
|
|
|
|
|
/* gossipd will refuse to announce the second one, sure, but it's
|
|
|
|
* better to check and fail now if they've explicitly asked for it. */
|
|
|
|
wn = &ld->proposed_wireaddr[n].u.wireaddr;
|
|
|
|
for (size_t i = 0; i < n; i++) {
|
|
|
|
const struct wireaddr *wi;
|
|
|
|
|
|
|
|
if (ld->proposed_listen_announce[i] != ADDR_ANNOUNCE)
|
|
|
|
continue;
|
|
|
|
assert(ld->proposed_wireaddr[i].itype == ADDR_INTERNAL_WIREADDR);
|
|
|
|
wi = &ld->proposed_wireaddr[i].u.wireaddr;
|
|
|
|
|
|
|
|
if (wn->type != wi->type)
|
|
|
|
continue;
|
|
|
|
return tal_fmt(NULL, "Cannot announce address %s;"
|
|
|
|
" already have %s which is the same type",
|
|
|
|
type_to_string(tmpctx, struct wireaddr, wn),
|
|
|
|
type_to_string(tmpctx, struct wireaddr, wi));
|
|
|
|
}
|
|
|
|
return NULL;
|
2018-05-07 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2018-03-18 13:22:21 +01:00
|
|
|
static void opt_show_u64(char buf[OPT_SHOW_LEN], const u64 *u)
|
|
|
|
{
|
|
|
|
snprintf(buf, OPT_SHOW_LEN, "%"PRIu64, *u);
|
|
|
|
}
|
2017-01-04 03:52:29 +01:00
|
|
|
static void opt_show_u32(char buf[OPT_SHOW_LEN], const u32 *u)
|
|
|
|
{
|
|
|
|
snprintf(buf, OPT_SHOW_LEN, "%"PRIu32, *u);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void opt_show_s32(char buf[OPT_SHOW_LEN], const s32 *u)
|
|
|
|
{
|
|
|
|
snprintf(buf, OPT_SHOW_LEN, "%"PRIi32, *u);
|
|
|
|
}
|
|
|
|
|
2017-07-10 10:31:35 +02:00
|
|
|
static char *opt_set_network(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
ld->topology->bitcoind->chainparams = chainparams_for_network(arg);
|
|
|
|
if (!ld->topology->bitcoind->chainparams)
|
2017-07-10 10:31:35 +02:00
|
|
|
return tal_fmt(NULL, "Unknown network name '%s'", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-05-03 14:20:30 +02:00
|
|
|
static char *opt_set_testnet(struct lightningd *ld)
|
|
|
|
{
|
|
|
|
return opt_set_network("testnet", ld);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_mainnet(struct lightningd *ld)
|
|
|
|
{
|
|
|
|
return opt_set_network("bitcoin", ld);
|
|
|
|
}
|
|
|
|
|
2017-07-12 13:05:39 +02:00
|
|
|
static void opt_show_network(char buf[OPT_SHOW_LEN],
|
|
|
|
const struct lightningd *ld)
|
|
|
|
{
|
2017-10-22 15:34:44 +02:00
|
|
|
snprintf(buf, OPT_SHOW_LEN, "%s", get_chainparams(ld)->network_name);
|
2017-07-12 13:05:39 +02:00
|
|
|
}
|
|
|
|
|
2017-10-23 07:05:28 +02:00
|
|
|
static char *opt_set_rgb(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2017-10-23 07:05:28 +02:00
|
|
|
ld->rgb = tal_free(ld->rgb);
|
|
|
|
/* BOLT #7:
|
|
|
|
*
|
2018-06-28 04:01:21 +02:00
|
|
|
* - Note: the first byte of `rgb_color` is the red value, the second
|
|
|
|
* byte is the green value, and the last byte is the blue value.
|
2018-06-17 12:13:44 +02:00
|
|
|
*/
|
2017-10-23 07:05:28 +02:00
|
|
|
ld->rgb = tal_hexdata(ld, arg, strlen(arg));
|
2018-07-28 08:00:16 +02:00
|
|
|
if (!ld->rgb || tal_count(ld->rgb) != 3)
|
2017-10-23 07:05:28 +02:00
|
|
|
return tal_fmt(NULL, "rgb '%s' is not six hex digits", arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *opt_set_alias(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
|
|
|
|
2017-10-23 07:05:28 +02:00
|
|
|
ld->alias = tal_free(ld->alias);
|
|
|
|
/* BOLT #7:
|
|
|
|
*
|
|
|
|
* * [`32`:`alias`]
|
|
|
|
*...
|
2018-06-17 12:13:44 +02:00
|
|
|
* - MUST set `alias` to a valid UTF-8 string, with any
|
|
|
|
* `alias` trailing-bytes equal to 0.
|
2017-10-23 07:05:28 +02:00
|
|
|
*/
|
|
|
|
if (strlen(arg) > 32)
|
|
|
|
return tal_fmt(NULL, "Alias '%s' is over 32 characters", arg);
|
2017-11-24 14:43:31 +01:00
|
|
|
ld->alias = tal_arrz(ld, u8, 33);
|
|
|
|
strncpy((char*)ld->alias, arg, 32);
|
2017-10-23 07:05:28 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-02-22 10:42:58 +01:00
|
|
|
static char *opt_set_offline(struct lightningd *ld)
|
|
|
|
{
|
2018-05-07 05:44:40 +02:00
|
|
|
ld->reconnect = false;
|
|
|
|
ld->listen = false;
|
|
|
|
|
2018-02-22 10:42:58 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-05-10 01:18:24 +02:00
|
|
|
static char *opt_add_proxy_addr(const char *arg, struct lightningd *ld)
|
2018-05-10 01:18:19 +02:00
|
|
|
{
|
2018-05-15 12:19:59 +02:00
|
|
|
bool needed_dns = false;
|
2018-05-10 01:18:24 +02:00
|
|
|
tal_free(ld->proxyaddr);
|
2018-05-10 01:18:19 +02:00
|
|
|
|
2018-05-10 01:18:23 +02:00
|
|
|
/* We use a tal_arr here, so we can marshal it to gossipd */
|
2018-05-10 01:18:24 +02:00
|
|
|
ld->proxyaddr = tal_arr(ld, struct wireaddr, 1);
|
2018-05-10 01:18:23 +02:00
|
|
|
|
2018-05-10 04:55:15 +02:00
|
|
|
if (!parse_wireaddr(arg, ld->proxyaddr, 9050,
|
|
|
|
ld->use_proxy_always ? &needed_dns : NULL,
|
2018-05-10 01:18:24 +02:00
|
|
|
NULL)) {
|
2018-05-10 04:55:15 +02:00
|
|
|
return tal_fmt(NULL, "Unable to parse Tor proxy address '%s' %s",
|
|
|
|
arg, needed_dns ? " (needed dns)" : "");
|
2018-05-10 01:18:19 +02:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-09-20 13:46:50 +02:00
|
|
|
static char *opt_add_plugin(const char *arg, struct lightningd *ld)
|
|
|
|
{
|
|
|
|
plugin_register(ld->plugins, arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
static void config_register_opts(struct lightningd *ld)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
2018-07-11 05:11:09 +02:00
|
|
|
opt_register_early_arg("--conf=<file>", opt_set_talstr, NULL,
|
|
|
|
&ld->config_filename,
|
|
|
|
"Specify configuration file. Relative paths will be prefixed by lightning-dir location. (default: config)");
|
2018-09-20 13:46:50 +02:00
|
|
|
|
|
|
|
/* Register plugins as an early argc, so we can initialize them and have
|
|
|
|
* them register more command line options */
|
|
|
|
opt_register_early_arg("--plugin", opt_add_plugin, NULL, ld,
|
|
|
|
"Add a plugin to be run.");
|
|
|
|
|
2018-02-16 03:00:41 +01:00
|
|
|
opt_register_noarg("--daemon", opt_set_bool, &ld->daemon,
|
|
|
|
"Run in the background, suppress stdout/stderr");
|
2018-01-16 10:24:46 +01:00
|
|
|
opt_register_arg("--ignore-fee-limits", opt_set_bool_arg, opt_show_bool,
|
|
|
|
&ld->config.ignore_fee_limits,
|
|
|
|
"(DANGEROUS) allow peer to set any feerate");
|
2018-05-17 06:46:22 +02:00
|
|
|
opt_register_arg("--watchtime-blocks", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.locktime_blocks,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Blocks before peer can unilaterally spend funds");
|
|
|
|
opt_register_arg("--max-locktime-blocks", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.locktime_max,
|
2018-06-15 15:35:55 +02:00
|
|
|
"Maximum blocks funds may be locked for");
|
2018-05-17 06:46:22 +02:00
|
|
|
opt_register_arg("--funding-confirms", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.anchor_confirms,
|
2018-05-17 06:46:22 +02:00
|
|
|
"Confirmations required for funding transaction");
|
2017-01-04 03:52:29 +01:00
|
|
|
opt_register_arg("--commit-fee-min=<percent>", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.commitment_fee_min_percent,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Minimum percentage of fee to accept for commitment");
|
|
|
|
opt_register_arg("--commit-fee-max=<percent>", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.commitment_fee_max_percent,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Maximum percentage of fee to accept for commitment (0 for unlimited)");
|
|
|
|
opt_register_arg("--commit-fee=<percent>", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.commitment_fee_percent,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Percentage of fee to request for their commitment");
|
2017-10-23 06:16:57 +02:00
|
|
|
opt_register_arg("--cltv-delta", opt_set_u32, opt_show_u32,
|
|
|
|
&ld->config.cltv_expiry_delta,
|
2018-10-22 06:15:35 +02:00
|
|
|
"Number of blocks for cltv_expiry_delta");
|
2017-10-23 06:16:57 +02:00
|
|
|
opt_register_arg("--cltv-final", opt_set_u32, opt_show_u32,
|
|
|
|
&ld->config.cltv_final,
|
2018-10-22 06:15:35 +02:00
|
|
|
"Number of blocks for final cltv_expiry");
|
2018-05-17 06:46:22 +02:00
|
|
|
opt_register_arg("--commit-time=<millseconds>",
|
|
|
|
opt_set_u32, opt_show_u32,
|
|
|
|
&ld->config.commit_time_ms,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Time after changes before sending out COMMIT");
|
|
|
|
opt_register_arg("--fee-base", opt_set_u32, opt_show_u32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.fee_base,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Millisatoshi minimum to charge for HTLC");
|
2018-04-18 15:23:15 +02:00
|
|
|
opt_register_arg("--rescan", opt_set_s32, opt_show_s32,
|
|
|
|
&ld->config.rescan,
|
|
|
|
"Number of blocks to rescan from the current head, or "
|
|
|
|
"absolute blockheight if negative");
|
2017-01-04 03:52:29 +01:00
|
|
|
opt_register_arg("--fee-per-satoshi", opt_set_s32, opt_show_s32,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->config.fee_per_satoshi,
|
2017-01-04 03:52:29 +01:00
|
|
|
"Microsatoshi fee for every satoshi in HTLC");
|
2018-05-07 05:53:21 +02:00
|
|
|
opt_register_arg("--addr", opt_add_addr, NULL,
|
2017-10-23 06:15:38 +02:00
|
|
|
ld,
|
2018-05-07 06:28:12 +02:00
|
|
|
"Set an IP address (v4 or v6) to listen on and announce to the network for incoming connections");
|
|
|
|
opt_register_arg("--bind-addr", opt_add_bind_addr, NULL,
|
|
|
|
ld,
|
|
|
|
"Set an IP address (v4 or v6) to listen on, but not announce");
|
|
|
|
opt_register_arg("--announce-addr", opt_add_announce_addr, NULL,
|
|
|
|
ld,
|
2018-05-10 01:18:19 +02:00
|
|
|
"Set an IP address (v4 or v6) or .onion v2/v3 to announce, but not listen on");
|
2018-05-07 06:28:12 +02:00
|
|
|
|
2018-02-22 10:42:58 +01:00
|
|
|
opt_register_noarg("--offline", opt_set_offline, ld,
|
2018-05-07 05:53:21 +02:00
|
|
|
"Start in offline-mode (do not automatically reconnect and do not accept incoming connections)");
|
2018-05-07 06:28:12 +02:00
|
|
|
opt_register_arg("--autolisten", opt_set_bool_arg, opt_show_bool,
|
|
|
|
&ld->autolisten,
|
|
|
|
"If true, listen on default port and announce if it seems to be a public interface");
|
2017-07-10 10:31:35 +02:00
|
|
|
|
2017-10-22 15:34:44 +02:00
|
|
|
opt_register_early_arg("--network", opt_set_network, opt_show_network,
|
|
|
|
ld,
|
|
|
|
"Select the network parameters (bitcoin, testnet,"
|
2018-01-22 15:36:54 +01:00
|
|
|
" regtest, litecoin or litecoin-testnet)");
|
2018-05-03 14:20:30 +02:00
|
|
|
opt_register_early_noarg("--testnet", opt_set_testnet, ld,
|
|
|
|
"Alias for --network=testnet");
|
|
|
|
opt_register_early_noarg("--mainnet", opt_set_mainnet, ld,
|
2018-05-04 02:27:39 +02:00
|
|
|
"Alias for --network=bitcoin");
|
2018-05-07 05:53:21 +02:00
|
|
|
opt_register_early_arg("--allow-deprecated-apis",
|
|
|
|
opt_set_bool_arg, opt_show_bool,
|
|
|
|
&deprecated_apis,
|
|
|
|
"Enable deprecated options, JSONRPC commands, fields, etc.");
|
2018-03-18 13:22:21 +01:00
|
|
|
opt_register_arg("--autocleaninvoice-cycle",
|
|
|
|
opt_set_u64, opt_show_u64,
|
|
|
|
&ld->ini_autocleaninvoice_cycle,
|
|
|
|
"Perform cleanup of expired invoices every given seconds, or do not autoclean if 0");
|
|
|
|
opt_register_arg("--autocleaninvoice-expired-by",
|
|
|
|
opt_set_u64, opt_show_u64,
|
|
|
|
&ld->ini_autocleaninvoice_cycle,
|
|
|
|
"If expired invoice autoclean enabled, invoices that have expired for at least this given seconds are cleaned");
|
2018-05-10 01:18:24 +02:00
|
|
|
opt_register_arg("--proxy", opt_add_proxy_addr, NULL,
|
2018-05-10 01:18:19 +02:00
|
|
|
ld,"Set a socks v5 proxy IP address and port");
|
2018-05-10 01:18:23 +02:00
|
|
|
opt_register_arg("--tor-service-password", opt_set_talstr, NULL,
|
|
|
|
&ld->tor_service_password,
|
|
|
|
"Set a Tor hidden service password");
|
2018-05-10 01:18:24 +02:00
|
|
|
|
|
|
|
/* Early, as it suppresses DNS lookups from cmdline too. */
|
|
|
|
opt_register_early_arg("--always-use-proxy",
|
|
|
|
opt_set_bool_arg, opt_show_bool,
|
|
|
|
&ld->use_proxy_always, "Use the proxy always");
|
2018-05-07 01:01:49 +02:00
|
|
|
|
2018-06-20 13:11:25 +02:00
|
|
|
opt_register_noarg("--disable-dns", opt_set_invbool, &ld->config.use_dns,
|
|
|
|
"Disable DNS lookups of peers");
|
|
|
|
|
2018-05-07 01:01:49 +02:00
|
|
|
#if DEVELOPER
|
|
|
|
opt_register_arg("--dev-max-funding-unconfirmed-blocks",
|
|
|
|
opt_set_u32, opt_show_u32,
|
|
|
|
&ld->max_funding_unconfirmed,
|
|
|
|
"Maximum number of blocks we wait for a channel "
|
|
|
|
"funding transaction to confirm, if we are the "
|
|
|
|
"fundee.");
|
|
|
|
#endif
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
|
|
|
|
2017-10-24 04:06:14 +02:00
|
|
|
#if DEVELOPER
|
2017-08-28 18:09:01 +02:00
|
|
|
static void dev_register_opts(struct lightningd *ld)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
2018-05-07 05:44:40 +02:00
|
|
|
opt_register_noarg("--dev-no-reconnect", opt_set_invbool,
|
|
|
|
&ld->reconnect,
|
2018-01-29 01:30:15 +01:00
|
|
|
"Disable automatic reconnect attempts");
|
2017-09-12 06:55:54 +02:00
|
|
|
opt_register_noarg("--dev-fail-on-subdaemon-fail", opt_set_bool,
|
|
|
|
&ld->dev_subdaemon_fail, opt_hidden);
|
2017-10-06 13:57:29 +02:00
|
|
|
opt_register_arg("--dev-debugger=<subdaemon>", opt_subd_debug, NULL,
|
2018-09-04 04:53:32 +02:00
|
|
|
ld, "Invoke gdb at start of <subdaemon>");
|
2017-10-06 13:57:29 +02:00
|
|
|
opt_register_arg("--dev-broadcast-interval=<ms>", opt_set_uintval,
|
2018-10-15 06:57:38 +02:00
|
|
|
opt_show_uintval, &ld->config.broadcast_interval_msec,
|
2018-01-10 03:43:23 +01:00
|
|
|
"Time between gossip broadcasts in milliseconds");
|
2017-10-06 13:57:29 +02:00
|
|
|
opt_register_arg("--dev-disconnect=<filename>", opt_subd_dev_disconnect,
|
|
|
|
NULL, ld, "File containing disconnection points");
|
2018-05-07 06:29:22 +02:00
|
|
|
opt_register_noarg("--dev-allow-localhost", opt_set_bool,
|
|
|
|
&ld->dev_allow_localhost,
|
|
|
|
"Announce and allow announcments for localhost address");
|
2018-05-17 06:08:24 +02:00
|
|
|
opt_register_arg("--dev-bitcoind-poll", opt_set_u32, opt_show_u32,
|
|
|
|
&ld->topology->poll_seconds,
|
|
|
|
"Time between polling for new transactions");
|
2018-06-11 19:50:37 +02:00
|
|
|
opt_register_arg("--dev-max-fee-multiplier", opt_set_u32, opt_show_u32,
|
|
|
|
&ld->config.max_fee_multiplier,
|
|
|
|
"Allow the fee proposed by the remote end to be up to "
|
|
|
|
"multiplier times higher than our own. Small values "
|
|
|
|
"will cause channels to be closed more often due to "
|
|
|
|
"fee fluctuations, large values may result in large "
|
|
|
|
"fees.");
|
2018-05-17 06:46:22 +02:00
|
|
|
|
|
|
|
opt_register_arg(
|
|
|
|
"--dev-channel-update-interval=<s>", opt_set_u32, opt_show_u32,
|
|
|
|
&ld->config.channel_update_interval,
|
|
|
|
"Time in seconds between channel updates for our own channels.");
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
2017-10-24 04:06:14 +02:00
|
|
|
#endif
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
static const struct config testnet_config = {
|
|
|
|
/* 6 blocks to catch cheating attempts. */
|
|
|
|
.locktime_blocks = 6,
|
|
|
|
|
2018-06-11 13:43:30 +02:00
|
|
|
/* They can have up to 14 days, maximumu value that lnd will ask for by default. */
|
|
|
|
/* FIXME Convince lnd to use more reasonable defaults... */
|
2018-06-15 15:35:55 +02:00
|
|
|
.locktime_max = 14 * 24 * 6,
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
/* We're fairly trusting, under normal circumstances. */
|
|
|
|
.anchor_confirms = 1,
|
|
|
|
|
|
|
|
/* Testnet fees are crazy, allow infinite feerange. */
|
|
|
|
.commitment_fee_min_percent = 0,
|
|
|
|
.commitment_fee_max_percent = 0,
|
|
|
|
|
|
|
|
/* We offer to pay 5 times 2-block fee */
|
|
|
|
.commitment_fee_percent = 500,
|
|
|
|
|
2017-10-23 06:16:57 +02:00
|
|
|
/* Be aggressive on testnet. */
|
|
|
|
.cltv_expiry_delta = 6,
|
2018-06-08 08:02:52 +02:00
|
|
|
.cltv_final = 10,
|
2017-10-23 06:16:57 +02:00
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Send commit 10msec after receiving; almost immediately. */
|
2018-05-17 06:46:22 +02:00
|
|
|
.commit_time_ms = 10,
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
/* Allow dust payments */
|
|
|
|
.fee_base = 1,
|
|
|
|
/* Take 0.001% */
|
|
|
|
.fee_per_satoshi = 10,
|
|
|
|
|
2018-01-10 03:43:23 +01:00
|
|
|
/* BOLT #7:
|
2018-06-17 12:13:44 +02:00
|
|
|
*
|
|
|
|
* - SHOULD flush outgoing gossip messages once every 60
|
|
|
|
* seconds, independently of the arrival times of the messages.
|
|
|
|
*/
|
2018-10-15 06:57:38 +02:00
|
|
|
.broadcast_interval_msec = 60000,
|
2018-01-10 03:43:23 +01:00
|
|
|
|
2018-01-09 15:45:07 +01:00
|
|
|
/* Send a keepalive update at least every week, prune every twice that */
|
|
|
|
.channel_update_interval = 1209600/2,
|
2018-01-16 10:24:46 +01:00
|
|
|
|
|
|
|
/* Testnet sucks */
|
|
|
|
.ignore_fee_limits = true,
|
2018-04-18 15:23:15 +02:00
|
|
|
|
|
|
|
/* Rescan 5 hours of blocks on testnet, it's reorg happy */
|
|
|
|
.rescan = 30,
|
2018-06-11 19:50:37 +02:00
|
|
|
|
|
|
|
/* Fees may be in the range our_fee - 10*our_fee */
|
2018-06-11 19:51:16 +02:00
|
|
|
.max_fee_multiplier = 10,
|
2018-06-20 13:11:25 +02:00
|
|
|
|
|
|
|
.use_dns = true,
|
2017-01-04 03:52:29 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/* aka. "Dude, where's my coins?" */
|
|
|
|
static const struct config mainnet_config = {
|
|
|
|
/* ~one day to catch cheating attempts. */
|
|
|
|
.locktime_blocks = 6 * 24,
|
|
|
|
|
2018-06-11 13:43:30 +02:00
|
|
|
/* They can have up to 14 days, maximumu value that lnd will ask for by default. */
|
|
|
|
/* FIXME Convince lnd to use more reasonable defaults... */
|
2018-06-15 15:35:55 +02:00
|
|
|
.locktime_max = 14 * 24 * 6,
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
/* We're fairly trusting, under normal circumstances. */
|
|
|
|
.anchor_confirms = 3,
|
|
|
|
|
|
|
|
/* Insist between 2 and 20 times the 2-block fee. */
|
|
|
|
.commitment_fee_min_percent = 200,
|
|
|
|
.commitment_fee_max_percent = 2000,
|
|
|
|
|
|
|
|
/* We offer to pay 5 times 2-block fee */
|
|
|
|
.commitment_fee_percent = 500,
|
|
|
|
|
2017-10-23 06:16:57 +02:00
|
|
|
/* BOLT #2:
|
|
|
|
*
|
2018-06-17 12:13:44 +02:00
|
|
|
* 1. the `cltv_expiry_delta` for channels, `3R+2G+2S`: if in doubt, a
|
|
|
|
* `cltv_expiry_delta` of 12 is reasonable (R=2, G=1, S=2)
|
|
|
|
*/
|
2017-10-23 06:16:57 +02:00
|
|
|
/* R = 2, G = 1, S = 3 */
|
|
|
|
.cltv_expiry_delta = 14,
|
|
|
|
|
|
|
|
/* BOLT #2:
|
|
|
|
*
|
2018-06-17 12:13:44 +02:00
|
|
|
* 4. the minimum `cltv_expiry` accepted for terminal payments: the
|
|
|
|
* worst case for the terminal node C is `2R+G+S` blocks */
|
2018-06-08 08:02:52 +02:00
|
|
|
.cltv_final = 10,
|
2017-10-23 06:16:57 +02:00
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Send commit 10msec after receiving; almost immediately. */
|
2018-05-17 06:46:22 +02:00
|
|
|
.commit_time_ms = 10,
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
/* Discourage dust payments */
|
2018-01-19 15:59:20 +01:00
|
|
|
.fee_base = 1000,
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Take 0.001% */
|
|
|
|
.fee_per_satoshi = 10,
|
|
|
|
|
2018-01-10 03:43:23 +01:00
|
|
|
/* BOLT #7:
|
2018-06-17 12:13:44 +02:00
|
|
|
*
|
|
|
|
* - SHOULD flush outgoing gossip messages once every 60
|
|
|
|
* seconds, independently of the arrival times of the messages.
|
|
|
|
*/
|
2018-10-15 06:57:38 +02:00
|
|
|
.broadcast_interval_msec = 60000,
|
2018-01-10 03:43:23 +01:00
|
|
|
|
2018-01-09 15:45:07 +01:00
|
|
|
/* Send a keepalive update at least every week, prune every twice that */
|
|
|
|
.channel_update_interval = 1209600/2,
|
2018-01-16 10:24:46 +01:00
|
|
|
|
|
|
|
/* Mainnet should have more stable fees */
|
|
|
|
.ignore_fee_limits = false,
|
2018-04-18 15:23:15 +02:00
|
|
|
|
|
|
|
/* Rescan 2.5 hours of blocks on startup, it's not so reorg happy */
|
|
|
|
.rescan = 15,
|
2018-06-11 19:50:37 +02:00
|
|
|
|
|
|
|
/* Fees may be in the range our_fee - 10*our_fee */
|
2018-06-11 19:51:16 +02:00
|
|
|
.max_fee_multiplier = 10,
|
2018-06-20 13:11:25 +02:00
|
|
|
|
|
|
|
.use_dns = true,
|
2017-01-04 03:52:29 +01:00
|
|
|
};
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
static void check_config(struct lightningd *ld)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
|
|
|
/* We do this by ensuring it's less than the minimum we would accept. */
|
2017-08-28 18:09:01 +02:00
|
|
|
if (ld->config.commitment_fee_max_percent != 0
|
|
|
|
&& ld->config.commitment_fee_max_percent
|
|
|
|
< ld->config.commitment_fee_min_percent)
|
2017-01-04 03:52:29 +01:00
|
|
|
fatal("Commitment fee invalid min-max %u-%u",
|
2017-08-28 18:09:01 +02:00
|
|
|
ld->config.commitment_fee_min_percent,
|
|
|
|
ld->config.commitment_fee_max_percent);
|
2017-01-04 03:52:29 +01:00
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
if (ld->config.anchor_confirms == 0)
|
2017-01-04 03:52:29 +01:00
|
|
|
fatal("anchor-confirms must be greater than zero");
|
2018-05-10 01:18:23 +02:00
|
|
|
|
2018-05-10 01:18:24 +02:00
|
|
|
if (ld->use_proxy_always && !ld->proxyaddr)
|
|
|
|
fatal("--always-use-proxy needs --proxy");
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
static void setup_default_config(struct lightningd *ld)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
2017-08-28 18:09:01 +02:00
|
|
|
if (get_chainparams(ld)->testnet)
|
|
|
|
ld->config = testnet_config;
|
2017-01-04 03:52:29 +01:00
|
|
|
else
|
2017-08-28 18:09:01 +02:00
|
|
|
ld->config = mainnet_config;
|
2018-02-20 00:00:09 +01:00
|
|
|
|
|
|
|
/* Set default PID file name to be per-network */
|
|
|
|
tal_free(ld->pidfile);
|
|
|
|
ld->pidfile = tal_fmt(ld, "lightningd-%s.pid", get_chainparams(ld)->network_name);
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* FIXME: make this nicer! */
|
2018-03-01 13:53:42 +01:00
|
|
|
static int config_parse_line_number = 0;
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
static void config_log_stderr_exit(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
char *msg;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
2018-03-01 13:53:42 +01:00
|
|
|
/* This is the format we expect:*/
|
2017-01-04 03:52:29 +01:00
|
|
|
if (streq(fmt, "%s: %.*s: %s")) {
|
|
|
|
const char *argv0 = va_arg(ap, const char *);
|
|
|
|
unsigned int len = va_arg(ap, unsigned int);
|
|
|
|
const char *arg = va_arg(ap, const char *);
|
|
|
|
const char *problem = va_arg(ap, const char *);
|
|
|
|
|
2018-03-01 13:53:42 +01:00
|
|
|
assert(argv0 != NULL);
|
2018-03-01 11:32:38 +01:00
|
|
|
assert(arg != NULL);
|
2018-03-01 13:53:42 +01:00
|
|
|
assert(problem != NULL);
|
|
|
|
/*mangle it to remove '--' and add the line number.*/
|
|
|
|
msg = tal_fmt(NULL, "%s line %d: %.*s: %s",
|
|
|
|
argv0, config_parse_line_number, len-2, arg+2, problem);
|
2017-01-04 03:52:29 +01:00
|
|
|
} else {
|
|
|
|
msg = tal_vfmt(NULL, fmt, ap);
|
|
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
fatal("%s", msg);
|
|
|
|
}
|
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
/**
|
|
|
|
* We turn the config file into cmdline arguments. @early tells us
|
|
|
|
* whether to parse early options only, or the non-early options.
|
|
|
|
*/
|
|
|
|
static void opt_parse_from_config(struct lightningd *ld, bool early)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
|
|
|
char *contents, **lines;
|
2018-03-01 13:53:42 +01:00
|
|
|
char **all_args; /*For each line: either argument string or NULL*/
|
|
|
|
char *argv[3];
|
2017-01-04 03:52:29 +01:00
|
|
|
int i, argc;
|
2018-10-13 06:09:49 +02:00
|
|
|
char *filename;
|
|
|
|
|
|
|
|
if (ld->config_filename != NULL)
|
|
|
|
filename = ld->config_filename;
|
|
|
|
else
|
|
|
|
filename = path_join(tmpctx, ld->config_dir, "config");
|
|
|
|
|
|
|
|
contents = grab_file(ld, filename);
|
2017-01-04 03:52:29 +01:00
|
|
|
|
2018-07-11 05:11:09 +02:00
|
|
|
/* The default config doesn't have to exist, but if the config was
|
|
|
|
* specified on the command line it has to exist. */
|
2017-01-04 03:52:29 +01:00
|
|
|
if (!contents) {
|
2018-07-11 05:11:09 +02:00
|
|
|
if ((errno != ENOENT) || (ld->config_filename != NULL))
|
2018-10-13 06:09:49 +02:00
|
|
|
fatal("Opening and reading %s: %s",
|
|
|
|
filename, strerror(errno));
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Now we can set up defaults, since no config file. */
|
2017-08-28 18:09:01 +02:00
|
|
|
setup_default_config(ld);
|
2017-01-04 03:52:29 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lines = tal_strsplit(contents, contents, "\r\n", STR_NO_EMPTY);
|
|
|
|
|
2018-03-01 13:53:42 +01:00
|
|
|
/* We have to keep all_args around, since opt will point into it */
|
|
|
|
all_args = tal_arr(ld, char *, tal_count(lines) - 1);
|
2017-01-04 03:52:29 +01:00
|
|
|
|
|
|
|
for (i = 0; i < tal_count(lines) - 1; i++) {
|
2018-03-01 13:53:42 +01:00
|
|
|
if (strstarts(lines[i], "#")) {
|
|
|
|
all_args[i] = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Only valid forms are "foo" and "foo=bar" */
|
|
|
|
all_args[i] = tal_fmt(all_args, "--%s", lines[i]);
|
|
|
|
}
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
2018-03-01 13:53:42 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
For each line we construct a fake argc,argv commandline.
|
|
|
|
argv[1] is the only element that changes between iterations.
|
|
|
|
*/
|
|
|
|
argc = 2;
|
|
|
|
argv[0] = "lightning config file";
|
2017-01-04 03:52:29 +01:00
|
|
|
argv[argc] = NULL;
|
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
if (early) {
|
|
|
|
for (i = 0; i < tal_count(all_args); i++) {
|
|
|
|
if (all_args[i] != NULL) {
|
|
|
|
config_parse_line_number = i + 1;
|
|
|
|
argv[1] = all_args[i];
|
|
|
|
opt_early_parse(argc, argv,
|
|
|
|
config_log_stderr_exit);
|
|
|
|
}
|
2018-03-01 13:53:42 +01:00
|
|
|
}
|
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
/* Now we can set up defaults, depending on whether testnet or
|
|
|
|
* not */
|
|
|
|
setup_default_config(ld);
|
|
|
|
} else {
|
2017-01-04 03:52:29 +01:00
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
for (i = 0; i < tal_count(all_args); i++) {
|
|
|
|
if (all_args[i] != NULL) {
|
|
|
|
config_parse_line_number = i + 1;
|
|
|
|
argv[1] = all_args[i];
|
|
|
|
opt_parse(&argc, argv, config_log_stderr_exit);
|
|
|
|
argc = 2; /* opt_parse might have changed it */
|
|
|
|
}
|
2018-03-01 13:53:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
tal_free(contents);
|
|
|
|
}
|
|
|
|
|
2018-09-03 02:42:27 +02:00
|
|
|
static char *test_subdaemons_and_exit(struct lightningd *ld)
|
2018-01-03 12:05:44 +01:00
|
|
|
{
|
2018-09-03 02:42:27 +02:00
|
|
|
test_subdaemons(ld);
|
2018-01-03 12:05:44 +01:00
|
|
|
exit(0);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-08-11 10:31:26 +02:00
|
|
|
static char *opt_lightningd_usage(struct lightningd *ld)
|
|
|
|
{
|
2018-08-01 02:53:20 +02:00
|
|
|
/* Reload config so that --help has the correct network defaults
|
|
|
|
* to display before it exits */
|
|
|
|
setup_default_config(ld);
|
2018-08-11 10:31:26 +02:00
|
|
|
char *extra = tal_fmt(NULL, "\nA bitcoin lightning daemon (default "
|
|
|
|
"values shown for network: %s).",
|
|
|
|
get_chainparams(ld)->network_name);
|
|
|
|
opt_usage_and_exit(extra);
|
|
|
|
tal_free(extra);
|
2018-08-01 02:53:20 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
void register_opts(struct lightningd *ld)
|
2017-01-04 03:52:29 +01:00
|
|
|
{
|
|
|
|
opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn);
|
|
|
|
|
2018-11-02 01:19:47 +01:00
|
|
|
opt_register_noarg("--help|-h", opt_lightningd_usage, ld,
|
2017-01-04 04:34:15 +01:00
|
|
|
"Print this message.");
|
2018-01-03 12:05:44 +01:00
|
|
|
opt_register_early_noarg("--test-daemons-only",
|
2018-09-03 02:42:27 +02:00
|
|
|
test_subdaemons_and_exit,
|
2018-01-03 12:05:44 +01:00
|
|
|
ld, opt_hidden);
|
2018-01-26 18:48:14 +01:00
|
|
|
|
2017-12-13 21:00:24 +01:00
|
|
|
opt_register_arg("--bitcoin-datadir", opt_set_talstr, NULL,
|
2017-08-28 18:09:01 +02:00
|
|
|
&ld->topology->bitcoind->datadir,
|
2017-01-04 03:52:29 +01:00
|
|
|
"-datadir arg for bitcoin-cli");
|
2017-10-23 07:05:28 +02:00
|
|
|
opt_register_arg("--rgb", opt_set_rgb, NULL, ld,
|
|
|
|
"RRGGBB hex color for node");
|
|
|
|
opt_register_arg("--alias", opt_set_alias, NULL, ld,
|
|
|
|
"Up to 32-byte alias for node");
|
2018-01-09 15:45:07 +01:00
|
|
|
|
2018-02-24 06:13:34 +01:00
|
|
|
opt_register_arg("--bitcoin-cli", opt_set_talstr, NULL,
|
|
|
|
&ld->topology->bitcoind->cli,
|
|
|
|
"bitcoin-cli pathname");
|
2018-01-30 15:17:58 +01:00
|
|
|
opt_register_arg("--bitcoin-rpcuser", opt_set_talstr, NULL,
|
|
|
|
&ld->topology->bitcoind->rpcuser,
|
|
|
|
"bitcoind RPC username");
|
|
|
|
opt_register_arg("--bitcoin-rpcpassword", opt_set_talstr, NULL,
|
|
|
|
&ld->topology->bitcoind->rpcpass,
|
|
|
|
"bitcoind RPC password");
|
|
|
|
opt_register_arg("--bitcoin-rpcconnect", opt_set_talstr, NULL,
|
|
|
|
&ld->topology->bitcoind->rpcconnect,
|
|
|
|
"bitcoind RPC host to connect to");
|
2018-03-25 13:57:52 +02:00
|
|
|
opt_register_arg("--bitcoin-rpcport", opt_set_talstr, NULL,
|
|
|
|
&ld->topology->bitcoind->rpcport,
|
|
|
|
"bitcoind RPC port");
|
2018-02-20 00:00:09 +01:00
|
|
|
opt_register_arg("--pid-file=<file>", opt_set_talstr, opt_show_charp,
|
|
|
|
&ld->pidfile,
|
|
|
|
"Specify pid file");
|
2018-01-30 15:17:58 +01:00
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
opt_register_logging(ld);
|
2017-01-04 03:52:29 +01:00
|
|
|
opt_register_version();
|
|
|
|
|
2017-08-28 18:09:01 +02:00
|
|
|
configdir_register_opts(ld, &ld->config_dir, &ld->rpc_filename);
|
|
|
|
config_register_opts(ld);
|
2017-10-24 04:06:14 +02:00
|
|
|
#if DEVELOPER
|
2017-08-28 18:09:01 +02:00
|
|
|
dev_register_opts(ld);
|
2017-10-24 04:06:14 +02:00
|
|
|
#endif
|
2017-02-24 06:52:56 +01:00
|
|
|
}
|
|
|
|
|
2017-10-23 07:05:28 +02:00
|
|
|
/* Names stolen from https://github.com/ternus/nsaproductgenerator/blob/master/nsa.js */
|
|
|
|
static const char *codename_adjective[]
|
|
|
|
= { "LOUD", "RED", "BLUE", "GREEN", "YELLOW", "IRATE", "ANGRY", "PEEVED",
|
|
|
|
"HAPPY", "SLIMY", "SLEEPY", "JUNIOR", "SLICKER", "UNITED", "SOMBER",
|
|
|
|
"BIZARRE", "ODD", "WEIRD", "WRONG", "LATENT", "CHILLY", "STRANGE", "LOUD",
|
|
|
|
"SILENT", "HOPPING", "ORANGE", "VIOLET", "VIOLENT", "LIGHTNING" };
|
|
|
|
|
|
|
|
static const char *codename_noun[]
|
|
|
|
= { "WHISPER", "FELONY", "MOON", "SUCKER", "PENGUIN", "WAFFLE", "MAESTRO",
|
|
|
|
"NIGHT", "TRINITY", "DEITY", "MONKEY", "ARK", "SQUIRREL", "IRON", "BOUNCE",
|
|
|
|
"FARM", "CHEF", "TROUGH", "NET", "TRAWL", "GLEE", "WATER", "SPORK", "PLOW",
|
|
|
|
"FEED", "SOUFFLE", "ROUTE", "BAGEL", "MONTANA", "ANALYST", "AUTO", "WATCH",
|
|
|
|
"PHOTO", "YARD", "SOURCE", "MONKEY", "SEAGULL", "TOLL", "SPAWN", "GOPHER",
|
|
|
|
"CHIPMUNK", "SET", "CALENDAR", "ARTIST", "CHASER", "SCAN", "TOTE", "BEAM",
|
|
|
|
"ENTOURAGE", "GENESIS", "WALK", "SPATULA", "RAGE", "FIRE", "MASTER" };
|
|
|
|
|
|
|
|
void setup_color_and_alias(struct lightningd *ld)
|
|
|
|
{
|
|
|
|
u8 der[PUBKEY_DER_LEN];
|
|
|
|
pubkey_to_der(der, &ld->id);
|
|
|
|
|
|
|
|
if (!ld->rgb)
|
|
|
|
/* You can't get much red by default */
|
|
|
|
ld->rgb = tal_dup_arr(ld, u8, der, 3, 0);
|
|
|
|
|
|
|
|
if (!ld->alias) {
|
|
|
|
u64 adjective, noun;
|
2018-02-22 11:00:33 +01:00
|
|
|
char *name;
|
2017-10-23 07:05:28 +02:00
|
|
|
|
|
|
|
memcpy(&adjective, der+3, sizeof(adjective));
|
|
|
|
memcpy(&noun, der+3+sizeof(adjective), sizeof(noun));
|
|
|
|
noun %= ARRAY_SIZE(codename_noun);
|
|
|
|
adjective %= ARRAY_SIZE(codename_adjective);
|
2018-02-22 11:00:33 +01:00
|
|
|
|
|
|
|
/* Only use 32 characters */
|
2018-09-29 08:34:01 +02:00
|
|
|
name = tal_fmt(ld, "%s%s",
|
2018-02-22 11:00:33 +01:00
|
|
|
codename_adjective[adjective],
|
|
|
|
codename_noun[noun]);
|
|
|
|
#if DEVELOPER
|
|
|
|
assert(strlen(name) < 32);
|
2018-09-29 08:34:01 +02:00
|
|
|
int taillen = 31 - strlen(name);
|
2018-02-22 11:00:33 +01:00
|
|
|
if (taillen > strlen(version()))
|
|
|
|
taillen = strlen(version());
|
|
|
|
/* Fit as much of end of version() as possible */
|
2018-09-29 08:34:01 +02:00
|
|
|
tal_append_fmt(&name, "-%s",
|
2018-02-22 11:00:33 +01:00
|
|
|
version() + strlen(version()) - taillen);
|
|
|
|
#endif
|
|
|
|
assert(strlen(name) <= 32);
|
2017-11-24 14:43:31 +01:00
|
|
|
ld->alias = tal_arrz(ld, u8, 33);
|
2018-02-22 11:00:33 +01:00
|
|
|
strcpy((char*)ld->alias, name);
|
|
|
|
tal_free(name);
|
2017-10-23 07:05:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
void handle_early_opts(struct lightningd *ld, int argc, char *argv[])
|
2017-02-24 06:52:56 +01:00
|
|
|
{
|
2018-08-01 02:53:20 +02:00
|
|
|
/* Load defaults. The actual values loaded here will be overwritten
|
|
|
|
* later by opt_parse_from_config. */
|
2017-12-09 00:15:00 +01:00
|
|
|
setup_default_config(ld);
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Get any configdir/testnet options first. */
|
2018-10-31 18:00:44 +01:00
|
|
|
opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit);
|
2017-01-04 03:52:29 +01:00
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
/* Now look for config file, but only handle the early
|
|
|
|
* options, others may be added on-demand */
|
|
|
|
opt_parse_from_config(ld, true);
|
|
|
|
}
|
2018-10-13 06:09:49 +02:00
|
|
|
|
2018-10-31 18:00:44 +01:00
|
|
|
void handle_opts(struct lightningd *ld, int argc, char *argv[])
|
|
|
|
{
|
2018-11-20 04:46:08 +01:00
|
|
|
/* Now look for config file, but only handle non-early
|
|
|
|
* options, early ones have been parsed in
|
|
|
|
* handle_early_opts */
|
|
|
|
opt_parse_from_config(ld, false);
|
|
|
|
|
2017-01-04 03:52:29 +01:00
|
|
|
/* Move to config dir, to save ourselves the hassle of path manip. */
|
2017-08-28 18:09:01 +02:00
|
|
|
if (chdir(ld->config_dir) != 0) {
|
2018-01-24 20:33:47 +01:00
|
|
|
log_unusual(ld->log, "Creating configuration directory %s",
|
2018-01-19 17:41:57 +01:00
|
|
|
ld->config_dir);
|
2017-08-28 18:09:01 +02:00
|
|
|
if (mkdir(ld->config_dir, 0700) != 0)
|
2017-01-04 03:52:29 +01:00
|
|
|
fatal("Could not make directory %s: %s",
|
2017-08-28 18:09:01 +02:00
|
|
|
ld->config_dir, strerror(errno));
|
|
|
|
if (chdir(ld->config_dir) != 0)
|
2017-01-04 03:52:29 +01:00
|
|
|
fatal("Could not change directory %s: %s",
|
2017-08-28 18:09:01 +02:00
|
|
|
ld->config_dir, strerror(errno));
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
opt_parse(&argc, argv, opt_log_stderr_exit);
|
|
|
|
if (argc != 1)
|
|
|
|
errx(1, "no arguments accepted");
|
|
|
|
|
2018-05-10 01:18:24 +02:00
|
|
|
/* We keep a separate variable rather than overriding use_proxy_always,
|
|
|
|
* so listconfigs shows the correct thing. */
|
|
|
|
if (tal_count(ld->proposed_wireaddr) != 0
|
|
|
|
&& all_tor_addresses(ld->proposed_wireaddr)) {
|
|
|
|
ld->pure_tor_setup = true;
|
|
|
|
if (!ld->proxyaddr)
|
|
|
|
log_info(ld->log, "Pure Tor setup with no --proxy:"
|
|
|
|
" you won't be able to make connections out");
|
|
|
|
}
|
2017-08-28 18:09:01 +02:00
|
|
|
check_config(ld);
|
2017-01-04 03:52:29 +01:00
|
|
|
}
|
2018-01-29 01:30:15 +01:00
|
|
|
|
|
|
|
/* FIXME: This is a hack! Expose somehow in ccan/opt.*/
|
|
|
|
/* Returns string after first '-'. */
|
|
|
|
static const char *first_name(const char *names, unsigned *len)
|
|
|
|
{
|
|
|
|
*len = strcspn(names + 1, "|= ");
|
|
|
|
return names + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *next_name(const char *names, unsigned *len)
|
|
|
|
{
|
|
|
|
names += *len;
|
|
|
|
if (names[0] == ' ' || names[0] == '=' || names[0] == '\0')
|
|
|
|
return NULL;
|
|
|
|
return first_name(names + 1, len);
|
|
|
|
}
|
|
|
|
|
2018-10-19 03:17:49 +02:00
|
|
|
static void json_add_opt_addrs(struct json_stream *response,
|
2018-05-07 06:28:12 +02:00
|
|
|
const char *name0,
|
2018-05-07 06:29:21 +02:00
|
|
|
const struct wireaddr_internal *wireaddrs,
|
2018-05-07 06:28:12 +02:00
|
|
|
const enum addr_listen_announce *listen_announce,
|
|
|
|
enum addr_listen_announce ala)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < tal_count(wireaddrs); i++) {
|
|
|
|
if (listen_announce[i] != ala)
|
|
|
|
continue;
|
|
|
|
json_add_string(response,
|
|
|
|
name0,
|
2018-05-07 06:29:21 +02:00
|
|
|
fmt_wireaddr_internal(name0, wireaddrs+i));
|
2018-05-07 06:28:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
static void add_config(struct lightningd *ld,
|
2018-10-19 03:17:49 +02:00
|
|
|
struct json_stream *response,
|
2018-01-29 01:30:15 +01:00
|
|
|
const struct opt_table *opt,
|
|
|
|
const char *name, size_t len)
|
|
|
|
{
|
|
|
|
char *name0 = tal_strndup(response, name, len);
|
|
|
|
const char *answer = NULL;
|
|
|
|
|
|
|
|
if (opt->type & OPT_NOARG) {
|
|
|
|
if (opt->cb == (void *)opt_usage_and_exit
|
|
|
|
|| opt->cb == (void *)version_and_exit
|
2018-05-03 14:20:30 +02:00
|
|
|
/* These two show up as --network= */
|
|
|
|
|| opt->cb == (void *)opt_set_testnet
|
|
|
|
|| opt->cb == (void *)opt_set_mainnet
|
2018-08-01 02:53:20 +02:00
|
|
|
|| opt->cb == (void *)opt_lightningd_usage
|
2018-09-03 02:42:27 +02:00
|
|
|
|| opt->cb == (void *)test_subdaemons_and_exit) {
|
2018-01-29 01:30:15 +01:00
|
|
|
/* These are not important */
|
|
|
|
} else if (opt->cb == (void *)opt_set_bool) {
|
|
|
|
const bool *b = opt->u.carg;
|
|
|
|
answer = tal_fmt(name0, "%s", *b ? "true" : "false");
|
2018-05-07 05:44:40 +02:00
|
|
|
} else if (opt->cb == (void *)opt_set_invbool) {
|
|
|
|
const bool *b = opt->u.carg;
|
|
|
|
answer = tal_fmt(name0, "%s", !*b ? "true" : "false");
|
|
|
|
} else if (opt->cb == (void *)opt_set_offline) {
|
|
|
|
answer = tal_fmt(name0, "%s",
|
|
|
|
(!ld->reconnect && !ld->listen)
|
|
|
|
? "true" : "false");
|
2018-01-29 01:30:15 +01:00
|
|
|
} else {
|
|
|
|
/* Insert more decodes here! */
|
2018-08-01 02:53:20 +02:00
|
|
|
assert(!"A noarg option was added but was not handled");
|
2018-01-29 01:30:15 +01:00
|
|
|
}
|
|
|
|
} else if (opt->type & OPT_HASARG) {
|
2018-05-17 06:46:22 +02:00
|
|
|
if (opt->desc == opt_hidden) {
|
|
|
|
/* Ignore hidden options (deprecated) */
|
|
|
|
} else if (opt->show) {
|
2018-01-29 01:30:15 +01:00
|
|
|
char *buf = tal_arr(name0, char, OPT_SHOW_LEN+1);
|
|
|
|
opt->show(buf, opt->u.carg);
|
|
|
|
|
|
|
|
if (streq(buf, "true") || streq(buf, "false")
|
|
|
|
|| strspn(buf, "0123456789.") == strlen(buf)) {
|
|
|
|
/* Let pure numbers and true/false through as
|
|
|
|
* literals. */
|
|
|
|
json_add_literal(response, name0,
|
|
|
|
buf, strlen(buf));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* opt_show_charp surrounds with "", strip them */
|
|
|
|
if (strstarts(buf, "\"")) {
|
|
|
|
buf[strlen(buf)-1] = '\0';
|
|
|
|
answer = buf + 1;
|
|
|
|
} else
|
|
|
|
answer = buf;
|
|
|
|
} else if (opt->cb_arg == (void *)opt_set_talstr
|
|
|
|
|| opt->cb_arg == (void *)opt_set_charp) {
|
|
|
|
const char *arg = *(char **)opt->u.carg;
|
|
|
|
if (arg)
|
|
|
|
answer = tal_fmt(name0, "%s", arg);
|
|
|
|
} else if (opt->cb_arg == (void *)opt_set_rgb) {
|
|
|
|
if (ld->rgb)
|
|
|
|
answer = tal_hexstr(name0, ld->rgb, 3);
|
|
|
|
} else if (opt->cb_arg == (void *)opt_set_alias) {
|
|
|
|
answer = (const char *)ld->alias;
|
|
|
|
} else if (opt->cb_arg == (void *)arg_log_to_file) {
|
|
|
|
answer = ld->logfile;
|
2018-05-07 05:53:21 +02:00
|
|
|
} else if (opt->cb_arg == (void *)opt_add_addr) {
|
2018-05-07 06:28:12 +02:00
|
|
|
json_add_opt_addrs(response, name0,
|
2018-05-07 06:29:22 +02:00
|
|
|
ld->proposed_wireaddr,
|
|
|
|
ld->proposed_listen_announce,
|
2018-05-07 06:28:12 +02:00
|
|
|
ADDR_LISTEN_AND_ANNOUNCE);
|
|
|
|
return;
|
|
|
|
} else if (opt->cb_arg == (void *)opt_add_bind_addr) {
|
|
|
|
json_add_opt_addrs(response, name0,
|
2018-05-07 06:29:22 +02:00
|
|
|
ld->proposed_wireaddr,
|
|
|
|
ld->proposed_listen_announce,
|
2018-05-07 06:28:12 +02:00
|
|
|
ADDR_LISTEN);
|
|
|
|
return;
|
|
|
|
} else if (opt->cb_arg == (void *)opt_add_announce_addr) {
|
|
|
|
json_add_opt_addrs(response, name0,
|
2018-05-07 06:29:22 +02:00
|
|
|
ld->proposed_wireaddr,
|
|
|
|
ld->proposed_listen_announce,
|
2018-05-07 06:28:12 +02:00
|
|
|
ADDR_ANNOUNCE);
|
2018-01-29 01:30:15 +01:00
|
|
|
return;
|
2018-05-10 01:18:24 +02:00
|
|
|
} else if (opt->cb_arg == (void *)opt_add_proxy_addr) {
|
|
|
|
if (ld->proxyaddr)
|
|
|
|
answer = fmt_wireaddr(name0, ld->proxyaddr);
|
2018-09-20 13:46:50 +02:00
|
|
|
} else if (opt->cb_arg == (void *)opt_add_plugin) {
|
2018-09-20 20:43:55 +02:00
|
|
|
json_add_opt_plugins(response, ld->plugins);
|
2018-01-29 01:30:15 +01:00
|
|
|
#if DEVELOPER
|
|
|
|
} else if (strstarts(name, "dev-")) {
|
|
|
|
/* Ignore dev settings */
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
/* Insert more decodes here! */
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-26 02:08:15 +02:00
|
|
|
if (answer) {
|
|
|
|
struct json_escaped *esc = json_escape(NULL, answer);
|
|
|
|
json_add_escaped_string(response, name0, take(esc));
|
|
|
|
}
|
2018-01-29 01:30:15 +01:00
|
|
|
tal_free(name0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_listconfigs(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
size_t i;
|
2018-10-19 03:17:49 +02:00
|
|
|
struct json_stream *response = NULL;
|
2018-07-20 03:14:02 +02:00
|
|
|
const jsmntok_t *configtok;
|
2018-01-29 01:30:15 +01:00
|
|
|
|
2018-07-20 03:14:02 +02:00
|
|
|
if (!param(cmd, buffer, params,
|
2018-08-15 17:16:02 +02:00
|
|
|
p_opt("config", json_tok_tok, &configtok),
|
2018-07-20 03:14:02 +02:00
|
|
|
NULL))
|
2018-01-29 01:30:15 +01:00
|
|
|
return;
|
|
|
|
|
2018-10-19 03:17:48 +02:00
|
|
|
if (!configtok) {
|
|
|
|
response = json_stream_success(cmd);
|
|
|
|
json_object_start(response, NULL);
|
2018-01-29 01:30:15 +01:00
|
|
|
json_add_string(response, "# version", version());
|
2018-10-19 03:17:48 +02:00
|
|
|
}
|
2018-01-29 01:30:15 +01:00
|
|
|
|
|
|
|
for (i = 0; i < opt_count; i++) {
|
|
|
|
unsigned int len;
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
/* FIXME: Print out comment somehow? */
|
|
|
|
if (opt_table[i].type == OPT_SUBTABLE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (name = first_name(opt_table[i].names, &len);
|
|
|
|
name;
|
|
|
|
name = next_name(name, &len)) {
|
|
|
|
/* Skips over first -, so just need to look for one */
|
|
|
|
if (name[0] != '-')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (configtok
|
|
|
|
&& !memeq(buffer + configtok->start,
|
|
|
|
configtok->end - configtok->start,
|
|
|
|
name + 1, len - 1))
|
|
|
|
continue;
|
|
|
|
|
2018-10-19 03:17:48 +02:00
|
|
|
if (!response) {
|
|
|
|
response = json_stream_success(cmd);
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
}
|
2018-01-29 01:30:15 +01:00
|
|
|
add_config(cmd->ld, response, &opt_table[i],
|
|
|
|
name+1, len-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-19 03:17:48 +02:00
|
|
|
if (configtok && !response) {
|
2018-05-24 23:40:18 +02:00
|
|
|
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
|
|
"Unknown config option '%.*s'",
|
2018-01-29 01:30:15 +01:00
|
|
|
configtok->end - configtok->start,
|
|
|
|
buffer + configtok->start);
|
|
|
|
return;
|
|
|
|
}
|
2018-10-19 03:17:48 +02:00
|
|
|
json_object_end(response);
|
2018-01-29 01:30:15 +01:00
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command listconfigs_command = {
|
|
|
|
"listconfigs",
|
|
|
|
json_listconfigs,
|
|
|
|
"List all configuration options, or with [config], just that one.",
|
|
|
|
|
|
|
|
.verbose = "listconfigs [config]\n"
|
|
|
|
"Outputs an object, with each field a config options\n"
|
|
|
|
"(Option names which start with # are comments)\n"
|
|
|
|
"With [config], object only has that field"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &listconfigs_command);
|