From adab9eb301d44fa64651555c40bf877a329a2a7e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 8 Jul 2021 12:17:03 +0930 Subject: [PATCH] lightningd: add force-feerates option. Useful for regtest and testnet. Sure, you shouldn't use this on mainnet, but I haven't restricted it because our users are usually pretty clever. Signed-off-by: Rusty Russell Fixes: #1806 Changelog-Added: config: `force_feerates` option to allow overriding feerate estimates (mainly for regtest). --- doc/lightning-listconfigs.7 | 4 ++- doc/lightning-listconfigs.7.md | 3 +- doc/lightningd-config.5 | 17 +++++++++- doc/lightningd-config.5.md | 13 ++++++++ doc/schemas/listconfigs.schema.json | 4 +++ lightningd/bitcoind.c | 6 ++++ lightningd/chaintopology.h | 1 + lightningd/lightningd.c | 5 +++ lightningd/lightningd.h | 4 +++ lightningd/options.c | 51 +++++++++++++++++++++++++++++ tests/test_misc.py | 45 +++++++++++++++++++++++++ 11 files changed, 150 insertions(+), 3 deletions(-) diff --git a/doc/lightning-listconfigs.7 b/doc/lightning-listconfigs.7 index 851004c8bdd0..6ae3b742ac9e 100644 --- a/doc/lightning-listconfigs.7 +++ b/doc/lightning-listconfigs.7 @@ -148,6 +148,8 @@ On success, an object is returned, containing: .IP \[bu] \fBlog-timestamps\fR (boolean, optional): \fBlog-timestamps\fR field from config or cmdline, or default .IP \[bu] +\fBforce-feerates\fR (string, optional): \fBforce-feerates\fR field from config or cmdline, if any +.IP \[bu] \fBsubdaemon\fR (string, optional): \fBsubdaemon\fR fields from config or cmdline if any (can be more than one) .IP \[bu] \fBtor-service-password\fR (string, optional): \fBtor-service-password\fR field from config or cmdline, if any @@ -268,4 +270,4 @@ Vincenzo Palazzo \fI wrote the initial versi Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:f813a777c6e074907980797dafd798a48633469a59e1ac526e2581e2b1a14c83 +\" SHA256STAMP:4591f6c754162b2dcdf82a36d584a48795752d39a986bc1d39c49e0cdbea440f diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md index a3351bdd72cd..85301e074552 100644 --- a/doc/lightning-listconfigs.7.md +++ b/doc/lightning-listconfigs.7.md @@ -84,6 +84,7 @@ On success, an object is returned, containing: - **log-prefix** (string, optional): `log-prefix` field from config or cmdline, or default - **log-file** (string, optional): `log-file` field from config or cmdline, or default - **log-timestamps** (boolean, optional): `log-timestamps` field from config or cmdline, or default +- **force-feerates** (string, optional): `force-feerates` field from config or cmdline, if any - **subdaemon** (string, optional): `subdaemon` fields from config or cmdline if any (can be more than one) - **tor-service-password** (string, optional): `tor-service-password` field from config or cmdline, if any [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -203,4 +204,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:3c3f2cd354ef5b33ad34febd29b04b1861c62d545c6a5b9181eb2b2b3880258f) +[comment]: # ( SHA256STAMP:ad98179a7b6254a936d4fde179918b6a975e186adcbc396917a0c2ed2888519e) diff --git a/doc/lightningd-config.5 b/doc/lightningd-config.5 index 46990229802b..e991a2488230 100644 --- a/doc/lightningd-config.5 +++ b/doc/lightningd-config.5 @@ -316,6 +316,21 @@ How long to wait before sending commitment messages to the peer: in theory increasing this would reduce load, but your node would have to be extremely busy node for you to even notice\. + + \fBforce-feerates\fR==\fIVALUES\fR +Networks like regtest and testnet have unreliable fee estimates: we +usually treat them as the minumum (253 sats/kw) if we can't get them\. +This allows override of one or more of our standard feerates (see +\fBlightning-feerates\fR(7))\. Up to 5 values, separated by '/' can be +provided: if fewer are provided, then the final value is used for the +remainder\. The values are in per-kw (roughly 1/4 of bitcoind's per-kb +values), an in order are "opening", "mutual_close", +"unilateral_close", "delayed_to_us", "htlc_resolution", and "penalty"\. + + +You would usually put this option in the per-chain config file, to avoid +setting it on Bitcoin mainnet! e\.g\. \fB~rusty/.lightning/regtest/config\fR\. + .SH Lightning channel and HTLC options \fBlarge-channels\fR @@ -635,4 +650,4 @@ Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Note: the modules in the ccan/ directory have their own licenses, but the rest of the code is covered by the BSD-style MIT license\. -\" SHA256STAMP:40c9f5e9e4ee5257e25a1fc196d2c85c3bc5b21d3f390a4e7fafa031c4e7ad5e +\" SHA256STAMP:d456e1acd004f9528d8772231afdecff1aaa01d80161c833483f6f078f4c7d70 diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index 24a3452ef882..6ef3272a016d 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -257,6 +257,19 @@ How long to wait before sending commitment messages to the peer: in theory increasing this would reduce load, but your node would have to be extremely busy node for you to even notice. + **force-feerates**==*VALUES* +Networks like regtest and testnet have unreliable fee estimates: we +usually treat them as the minumum (253 sats/kw) if we can't get them. +This allows override of one or more of our standard feerates (see +lightning-feerates(7)). Up to 5 values, separated by '/' can be +provided: if fewer are provided, then the final value is used for the +remainder. The values are in per-kw (roughly 1/4 of bitcoind's per-kb +values), an in order are "opening", "mutual_close", +"unilateral_close", "delayed_to_us", "htlc_resolution", and "penalty". + +You would usually put this option in the per-chain config file, to avoid +setting it on Bitcoin mainnet! e.g. `~rusty/.lightning/regtest/config`. + ### Lightning channel and HTLC options **large-channels** diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json index d0844021995a..877cea2107a4 100644 --- a/doc/schemas/listconfigs.schema.json +++ b/doc/schemas/listconfigs.schema.json @@ -235,6 +235,10 @@ "type": "boolean", "description": "`log-timestamps` field from config or cmdline, or default" }, + "force-feerates": { + "type": "string", + "description": "force-feerate configuration setting, if any" + }, "subdaemon": { "type": "string", "description": "`subdaemon` fields from config or cmdline if any (can be more than one)" diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 8fae04765e5b..1adfe47107fd 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -187,6 +187,12 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, bitcoin_plugin_error(call->bitcoind, buf, toks, "estimatefees", "missing '%s' field", feerate_name(f)); + /* We still use the bcli plugin for min and max, even with + * force_feerates */ + if (f < tal_count(call->bitcoind->ld->force_feerates)) { + feerates[f] = call->bitcoind->ld->force_feerates[f]; + continue; + } /* FIXME: We could trawl recent blocks for median fee... */ if (!json_to_u32(buf, feeratetok, &feerates[f])) { diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index cb31bad2c4af..0b6a5e0203dc 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -22,6 +22,7 @@ struct txwatch; /* FIXME: move all feerate stuff out to new lightningd/feerate.[ch] files */ enum feerate { + /* DO NOT REORDER: force-feerates uses this order! */ FEERATE_OPENING, FEERATE_MUTUAL_CLOSE, FEERATE_UNILATERAL_CLOSE, diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 4272d7b6f1f9..96303dc7514a 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -290,6 +290,11 @@ static struct lightningd *new_lightningd(const tal_t *ctx) * each invoice we generate has a different set of channels. */ ld->rr_counter = 0; + /*~ Because fee estimates on testnet and regtest are unreliable, + * we allow overriding them with --force-feerates, in which + * case this is a pointer to an enum feerate-indexed array of values */ + ld->force_feerates = NULL; + return ld; } diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 9bd8f696a226..10aa39a41803 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -199,6 +199,10 @@ struct lightningd { /* RPC response to send once we've shut down. */ const char *stop_response; + /* Used these feerates instead of whatever bcli returns (up to + * FEERATE_PENALTY). */ + u32 *force_feerates; + #if DEVELOPER /* If we want to debug a subdaemon/plugin. */ const char *dev_debug_subprocess; diff --git a/lightningd/options.c b/lightningd/options.c index 99bc32faddf9..1fceab9314e2 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -131,6 +131,51 @@ static char *opt_set_mode(const char *arg, mode_t *m) return NULL; } +static char *opt_force_feerates(const char *arg, struct lightningd *ld) +{ + char **vals = tal_strsplit(tmpctx, arg, "/", STR_EMPTY_OK); + size_t n; + + /* vals has NULL at end, enum feerate is 0 based */ + if (tal_count(vals) - 1 > FEERATE_PENALTY + 1) + return "Too many values"; + + if (!ld->force_feerates) + ld->force_feerates = tal_arr(ld, u32, FEERATE_PENALTY + 1); + + n = 0; + for (size_t i = 0; i < tal_count(ld->force_feerates); i++) { + char *err = opt_set_u32(vals[n], &ld->force_feerates[i]); + if (err) + return err; + fprintf(stderr, "Set feerate %zu based on val %zu\n", i, n); + if (vals[n+1]) + n++; + } + return NULL; +} + +static char *fmt_force_feerates(const tal_t *ctx, const u32 *force_feerates) +{ + char *ret; + size_t last; + + if (!force_feerates) + return NULL; + + ret = tal_fmt(ctx, "%i", force_feerates[0]); + last = 0; + for (size_t i = 1; i < tal_count(force_feerates); i++) { + if (force_feerates[i] == force_feerates[i-1]) + continue; + /* Different? Catchup! */ + for (size_t j = last + 1; j <= i; j++) + tal_append_fmt(&ret, "/%i", force_feerates[j]); + last = i; + } + return ret; +} + #if EXPERIMENTAL_FEATURES static char *opt_set_accept_extra_tlv_types(const char *arg, struct lightningd *ld) @@ -1003,6 +1048,10 @@ static void register_opts(struct lightningd *ld) "Set the file mode (permissions) for the " "JSON-RPC socket"); + opt_register_arg("--force-feerates", + opt_force_feerates, NULL, ld, + "Set testnet/regtest feerates in sats perkw, opening/mutual_close/unlateral_close/delayed_to_us/htlc_resolution/penalty: if fewer specified, last number applies to remainder"); + opt_register_arg("--subdaemon", opt_subdaemon, NULL, ld, "Arg specified as SUBDAEMON:PATH. " "Specifies an alternate subdaemon binary. " @@ -1423,6 +1472,8 @@ static void add_config(struct lightningd *ld, json_add_opt_log_levels(response, ld->log); } else if (opt->cb_arg == (void *)opt_disable_plugin) { json_add_opt_disable_plugins(response, ld->plugins); + } else if (opt->cb_arg == (void *)opt_force_feerates) { + answer = fmt_force_feerates(name0, ld->force_feerates); } else if (opt->cb_arg == (void *)opt_important_plugin) { /* Do nothing, this is already handled by * opt_add_plugin. */ diff --git a/tests/test_misc.py b/tests/test_misc.py index 28c915d87e20..4e76c847f062 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2561,3 +2561,48 @@ def test_getlog(node_factory): # This should not logs = l1.rpc.getlog(level='io')['log'] assert [l for l in logs if l['type'] == 'SKIPPED'] == [] + + +def test_force_feerates(node_factory): + l1 = node_factory.get_node(options={'force-feerates': 1111}) + assert l1.rpc.listconfigs()['force-feerates'] == '1111' + + assert l1.rpc.feerates('perkw')['perkw'] == { + "opening": 1111, + "mutual_close": 1111, + "unilateral_close": 1111, + "delayed_to_us": 1111, + "htlc_resolution": 1111, + "penalty": 1111, + "min_acceptable": 1875, + "max_acceptable": 150000} + + l1.stop() + l1.daemon.opts['force-feerates'] = '1111/2222' + l1.start() + + assert l1.rpc.listconfigs()['force-feerates'] == '1111/2222' + assert l1.rpc.feerates('perkw')['perkw'] == { + "opening": 1111, + "mutual_close": 2222, + "unilateral_close": 2222, + "delayed_to_us": 2222, + "htlc_resolution": 2222, + "penalty": 2222, + "min_acceptable": 1875, + "max_acceptable": 150000} + + l1.stop() + l1.daemon.opts['force-feerates'] = '1111/2222/3333/4444/5555/6666' + l1.start() + + assert l1.rpc.listconfigs()['force-feerates'] == '1111/2222/3333/4444/5555/6666' + assert l1.rpc.feerates('perkw')['perkw'] == { + "opening": 1111, + "mutual_close": 2222, + "unilateral_close": 3333, + "delayed_to_us": 4444, + "htlc_resolution": 5555, + "penalty": 6666, + "min_acceptable": 1875, + "max_acceptable": 150000}