From b654abccad0ac2077ab78cabdfa223b59abcfa21 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 18:01:42 -0400 Subject: [PATCH 1/4] bitcoin: add feerate_relay_floor for a backend minimum above 1 sat/vB --- bitcoin/feerate.c | 15 ++++++++ bitcoin/feerate.h | 10 ++++++ bitcoin/test/run-feerate_relay_floor.c | 49 ++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 bitcoin/test/run-feerate_relay_floor.c diff --git a/bitcoin/feerate.c b/bitcoin/feerate.c index c0cca0608f37..2511f91910d1 100644 --- a/bitcoin/feerate.c +++ b/bitcoin/feerate.c @@ -25,6 +25,21 @@ u32 feerate_from_style(u32 feerate, enum feerate_style style) abort(); } +u32 feerate_relay_floor(u32 floor_perkw) +{ + u64 rate; + + if (floor_perkw <= BITCOIND_MINRELAYTXFEE_PER_KW) + return FEERATE_FLOOR; + + rate = ((u64)floor_perkw * (MINIMUM_TX_WEIGHT + 3) + + MINIMUM_TX_WEIGHT - 1) / MINIMUM_TX_WEIGHT + + (2000 + MINIMUM_TX_WEIGHT - 1) / MINIMUM_TX_WEIGHT; + if (rate > UINT_MAX) + return UINT_MAX; + return rate; +} + u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style) { switch (style) { diff --git a/bitcoin/feerate.h b/bitcoin/feerate.h index b63a1feddee4..ffb3febca0a1 100644 --- a/bitcoin/feerate.h +++ b/bitcoin/feerate.h @@ -76,6 +76,16 @@ static inline u32 feerate_floor_check(void) return FEERATE_FLOOR; } +/* The lowest feerate we should pay ourselves, given bitcoind's floor in + * perkw (ie. a minimum of at most 4 * floor_perkw sat/kvB). + * + * At 1 sat/vB this is FEERATE_FLOOR. Above it FEERATE_FLOOR's slack no + * longer covers the rounding, so we use the worst case: bitcoind needs + * ceil(minimum * ceil(weight / 4) / 1000), we pay (feerate * weight) // 1000, + * and feerate * weight / 1000 - 1 >= minimum * (weight + 3) / 4000 + 1 is + * hardest to satisfy at MINIMUM_TX_WEIGHT. */ +u32 feerate_relay_floor(u32 floor_perkw); + u32 feerate_from_style(u32 feerate, enum feerate_style style); u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style); const char *feerate_style_name(enum feerate_style style); diff --git a/bitcoin/test/run-feerate_relay_floor.c b/bitcoin/test/run-feerate_relay_floor.c new file mode 100644 index 000000000000..bcdb750026e1 --- /dev/null +++ b/bitcoin/test/run-feerate_relay_floor.c @@ -0,0 +1,49 @@ +#include "config.h" +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* AUTOGENERATED MOCKS END */ + +/* What bitcoind charges at a minimum of perkvb sat/kvB. */ +static u64 bitcoind_min_fee(u64 perkvb, u64 weight) +{ + u64 vsize = (weight + 3) / 4; + return (perkvb * vsize + 999) / 1000; +} + +static bool relays(u64 perkvb, u32 feerate, u64 weight) +{ + return (u64)feerate * weight / 1000 >= bitcoind_min_fee(perkvb, weight); +} + +int main(int argc, const char *argv[]) +{ + common_setup(argv[0]); + + assert(feerate_relay_floor(0) == FEERATE_FLOOR); + assert(feerate_relay_floor(BITCOIND_MINRELAYTXFEE_PER_KW) == FEERATE_FLOOR); + assert(feerate_relay_floor(UINT32_MAX) == UINT32_MAX); + + /* Floors as bitcoind reports them (sat/kvB), converted as we do: + * every one up to 20 sat/vB, then a sample up to FEERATE_CEILING. + * Small weights are the hard case. */ + for (u64 perkvb = 1; perkvb < 4000000; perkvb += perkvb < 20000 ? 1 : 997) { + u32 floor = feerate_from_style(perkvb, FEERATE_PER_KBYTE); + u32 rate = feerate_relay_floor(floor); + + /* Never much more than bitcoind's own floor. */ + assert(perkvb <= 1000 + || (u64)rate * 4 <= perkvb + perkvb / 100 + 40); + + for (u64 weight = MINIMUM_TX_WEIGHT; weight < 800; weight++) + assert(relays(perkvb, rate, weight)); + for (u64 weight = 800; weight < 400000; weight += 9973) + assert(relays(perkvb, rate, weight)); + } + + common_shutdown(); + return 0; +} From bf1a77612c2060502c0b1608fe46b3d8f9ed6815 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 18:01:43 -0400 Subject: [PATCH 2/4] lightningd: pay at least the relay floor on our own transactions --- lightningd/bitcoind.c | 21 ++++++++++++++------ lightningd/bitcoind.h | 2 ++ lightningd/chaintopology.c | 30 +++++++++++++++++----------- lightningd/chaintopology.h | 8 ++++++++ lightningd/closing_control.c | 4 ++-- lightningd/dual_open_control.c | 4 ++-- lightningd/feerate.c | 2 +- lightningd/onchain_control.c | 4 ++-- lightningd/opening_control.c | 4 ++-- lightningd/test/run-jsonrpc.c | 6 +++--- tests/test_misc.py | 36 +++++++++++++++++----------------- 11 files changed, 74 insertions(+), 47 deletions(-) diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 317180e85ee5..9738fade12a2 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -183,6 +183,7 @@ static void bitcoin_plugin_send(struct bitcoind *bitcoind, struct estimatefee_call { struct bitcoind *bitcoind; void (*cb)(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *rates, void *); void *cb_arg; }; @@ -254,7 +255,7 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, { const jsmntok_t *resulttok, *floortok; struct feerate_est *feerates; - u32 floor; + u32 floor, relay_floor; resulttok = get_bitcoin_result(call->bitcoind, buf, toks, "estimatefees"); @@ -274,8 +275,6 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, /* Convert to perkw */ floor = feerate_from_style(floor, FEERATE_PER_KBYTE); - if (floor < FEERATE_FLOOR) - floor = FEERATE_FLOOR; /* Anything this high is a broken fee source, not a busy mempool: * clamp it rather than let it loose on the rest of lightningd. */ @@ -287,6 +286,14 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, floor = FEERATE_CEILING; } + /* Before raising floor to FEERATE_FLOOR, which would hide whether + * bitcoind's minimum is above 1 sat/vB. */ + relay_floor = feerate_relay_floor(floor); + if (relay_floor > FEERATE_CEILING) + relay_floor = FEERATE_CEILING; + if (floor < FEERATE_FLOOR) + floor = FEERATE_FLOOR; + /* FIXME: We could let this go below the dynamic floor, but we'd * need to know if the floor is because of their node's policy * (minrelaytxfee) or mempool conditions (mempoolminfee). */ @@ -301,11 +308,12 @@ static void estimatefees_callback(const char *buf, const jsmntok_t *toks, (u32)FEERATE_CEILING); feerates[i].rate = FEERATE_CEILING; } - if (feerates[i].rate < floor) - feerates[i].rate = floor; + if (feerates[i].rate < relay_floor) + feerates[i].rate = relay_floor; } - call->cb(call->bitcoind->ld, floor, feerates, call->cb_arg); + call->cb(call->bitcoind->ld, floor, relay_floor, feerates, + call->cb_arg); tal_free(call); } @@ -313,6 +321,7 @@ void bitcoind_estimate_fees_(const tal_t *ctx, struct bitcoind *bitcoind, void (*cb)(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *feerates, void *arg), void *cb_arg) diff --git a/lightningd/bitcoind.h b/lightningd/bitcoind.h index 462817c9cc72..ff202ce65626 100644 --- a/lightningd/bitcoind.h +++ b/lightningd/bitcoind.h @@ -60,6 +60,7 @@ struct bitcoind *new_bitcoind(const tal_t *ctx, (cb), (arg), \ struct lightningd *, \ u32, \ + u32, \ const struct feerate_est *), \ (arg)) @@ -67,6 +68,7 @@ void bitcoind_estimate_fees_(const tal_t *ctx, struct bitcoind *bitcoind, void (*cb)(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *feerates, void *arg), void *cb_arg); diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 80c9360a5125..d8ae18f6ce61 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -435,8 +435,8 @@ u32 feerate_for_deadline(const struct chain_topology *topo, u32 blockcount) u32 rate = interp_feerate(topo->feerates[0], blockcount); /* 0 is a special value, meaning "don't know" */ - if (rate && rate < topo->feerate_floor) - rate = topo->feerate_floor; + if (rate && rate < topo->relay_feerate_floor) + rate = topo->relay_feerate_floor; return rate; } @@ -460,12 +460,9 @@ u32 feerate_for_target(const struct chain_topology *topo, u64 deadline) blocks = deadline - blockheight; - /* Over 200 blocks, we *always* use min fee! */ - if (blocks > 200) - return FEERATE_FLOOR; /* Over 100 blocks, use min fee bitcoind will accept */ if (blocks > 100) - return get_feerate_floor(topo); + return get_relay_feerate_floor(topo); return feerate_for_deadline(topo, blocks); } @@ -492,8 +489,8 @@ static void smooth_one_feerate(const struct chain_topology *topo, if (abs((int)rate->rate - (int)feerate_smooth) > (0.1 * rate->rate)) rate->rate = feerate_smooth; - if (rate->rate < get_feerate_floor(topo)) - rate->rate = get_feerate_floor(topo); + if (rate->rate < get_relay_feerate_floor(topo)) + rate->rate = get_relay_feerate_floor(topo); if (rate->rate != feerate_smooth) log_debug(topo->log, @@ -540,6 +537,7 @@ static bool different_blockcounts(struct chain_topology *topo, static void update_feerates(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *rates TAKES, void *arg UNUSED) { @@ -548,6 +546,7 @@ static void update_feerates(struct lightningd *ld, struct chain_topology *topo = ld->topology; topo->feerate_floor = feerate_floor; + topo->relay_feerate_floor = relay_feerate_floor; /* Don't bother updating if we got no feerates; we'd rather have * historical ones, if any. */ @@ -587,10 +586,11 @@ static void update_feerates(struct lightningd *ld, static void update_feerates_repeat(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *rates TAKES, void *unused) { - update_feerates(ld, feerate_floor, rates, unused); + update_feerates(ld, feerate_floor, relay_feerate_floor, rates, unused); next_updatefee_timer(ld->topology); } @@ -701,6 +701,11 @@ u32 get_feerate_floor(const struct chain_topology *topo) return topo->feerate_floor; } +u32 get_relay_feerate_floor(const struct chain_topology *topo) +{ + return topo->relay_feerate_floor; +} + static struct command_result *json_feerates(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1407,16 +1412,18 @@ static void get_chaininfo_once(struct bitcoind *bitcoind, const char *chain, } struct feerates_once { - u32 feerate_floor; + u32 feerate_floor, relay_feerate_floor; struct feerate_est *rates; }; static void get_feerates_once(struct lightningd *ld, u32 feerate_floor, + u32 relay_feerate_floor, const struct feerate_est *rates TAKES, struct feerates_once *once) { once->feerate_floor = feerate_floor; + once->relay_feerate_floor = relay_feerate_floor; once->rates = tal_dup_talarr(once, struct feerate_est, rates); io_break(ld->topology); } @@ -1572,7 +1579,8 @@ void setup_topology(struct chain_topology *topo) chaininfo->ibd, topo, true); /* It's very useful to have feerates early */ - update_feerates(topo->ld, feerates->feerate_floor, feerates->rates, NULL); + update_feerates(topo->ld, feerates->feerate_floor, + feerates->relay_feerate_floor, feerates->rates, NULL); /* Get the first block, so we can initialize topography. */ bitcoind_getrawblockbyheight(topo, topo->bitcoind, blockscan_start, diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index 8657546eb4a2..906755d5c5ee 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -106,6 +106,9 @@ struct chain_topology { /* This is the lowest feerate that bitcoind is saying will broadcast. */ u32 feerate_floor; + /* And the lowest we pay ourselves, so our rounding doesn't fall + * short of it (see feerate_relay_floor). */ + u32 relay_feerate_floor; /* We keep last three feerates we got: this is useful for min/max. */ struct feerate_est *feerates[FEE_HISTORY_NUM]; @@ -164,6 +167,11 @@ struct txlocator { /* Get the minimum feerate that bitcoind will accept */ u32 get_feerate_floor(const struct chain_topology *topo); +/* Get the minimum feerate we use for our own transactions: a little above + * get_feerate_floor() when that is above 1 sat/vB. Use get_feerate_floor() + * for what we accept from a peer. */ +u32 get_relay_feerate_floor(const struct chain_topology *topo); + /* This is the number of blocks which would have to be mined to invalidate * the tx */ size_t get_tx_depth(const struct chain_topology *topo, diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index 529672f35be9..9549e6e21460 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -575,8 +575,8 @@ void peer_start_closingd(struct channel *channel, struct peer_fd *peer_fd) feerate = mutual_close_feerate(ld->topology); if (!feerate) { feerate = final_commit_feerate / 2; - if (feerate < get_feerate_floor(ld->topology)) - feerate = get_feerate_floor(ld->topology); + if (feerate < get_relay_feerate_floor(ld->topology)) + feerate = get_relay_feerate_floor(ld->topology); } max_feerate = calc_max_close_feerate(ld, channel); diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 25b34247f8be..3f1662fa5cf7 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -3357,7 +3357,7 @@ static struct command_result *openchannel_init(struct command *cmd, /* 0 from this means "unknown" */ anchor_feerate = unilateral_feerate(cmd->ld->topology, true); if (anchor_feerate == 0) { - anchor_feerate = get_feerate_floor(cmd->ld->topology); + anchor_feerate = get_relay_feerate_floor(cmd->ld->topology); assert(anchor_feerate); } @@ -4227,7 +4227,7 @@ static struct command_result *json_queryrates(struct command *cmd, /* 0 from this means "unknown" */ anchor_feerate = unilateral_feerate(cmd->ld->topology, true); if (anchor_feerate == 0) { - anchor_feerate = get_feerate_floor(cmd->ld->topology); + anchor_feerate = get_relay_feerate_floor(cmd->ld->topology); assert(anchor_feerate); } diff --git a/lightningd/feerate.c b/lightningd/feerate.c index 78027a56a4c2..2e668ddfa52b 100644 --- a/lightningd/feerate.c +++ b/lightningd/feerate.c @@ -88,7 +88,7 @@ static struct command_result *param_feerate_unchecked(struct command *cmd, **feerate = feerate_for_deadline(cmd->ld->topology, 6); return NULL; } else if (json_tok_streq(buffer, tok, "minimum")) { - **feerate = get_feerate_floor(cmd->ld->topology); + **feerate = get_relay_feerate_floor(cmd->ld->topology); return NULL; } diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index f99e258e5191..1a7a5dcb64d4 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -1014,8 +1014,8 @@ static struct bitcoin_tx *onchaind_tx_unsigned(const tal_t *ctx, /* Hmm, can't afford with recommended fee. Try increasing deadline! */ block_target++; - /* If we can't even afford at FEERATE_FLOOR, something is wrong! */ - if (feerate == FEERATE_FLOOR) { + /* If we can't even afford the floor, something is wrong! */ + if (feerate == get_relay_feerate_floor(ld->topology)) { amt = channel->our_config.dust_limit; /* Not quite true, but Never Happens */ *fee = AMOUNT_SAT(0); diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 1c497b7c137f..c931deac7f7d 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -1392,10 +1392,10 @@ static struct command_result *json_fundchannel_start(struct command *cmd, "Cannot estimate fees"); } - if (*feerate_non_anchor < get_feerate_floor(cmd->ld->topology)) { + if (*feerate_non_anchor < get_relay_feerate_floor(cmd->ld->topology)) { return command_fail(cmd, LIGHTNINGD, "Feerate for non-anchor (%u perkw) below feerate floor %u perkw", - *feerate_non_anchor, get_feerate_floor(cmd->ld->topology)); + *feerate_non_anchor, get_relay_feerate_floor(cmd->ld->topology)); } peer = peer_by_id(cmd->ld, id); diff --git a/lightningd/test/run-jsonrpc.c b/lightningd/test/run-jsonrpc.c index 611cc04825e0..0ecba8bdc972 100644 --- a/lightningd/test/run-jsonrpc.c +++ b/lightningd/test/run-jsonrpc.c @@ -26,9 +26,9 @@ void fatal(const char *fmt UNNEEDED, ...) /* Generated stub for feerate_for_deadline */ u32 feerate_for_deadline(const struct chain_topology *topo UNNEEDED, u32 blockcount UNNEEDED) { fprintf(stderr, "feerate_for_deadline called!\n"); abort(); } -/* Generated stub for get_feerate_floor */ -u32 get_feerate_floor(const struct chain_topology *topo UNNEEDED) -{ fprintf(stderr, "get_feerate_floor called!\n"); abort(); } +/* Generated stub for get_relay_feerate_floor */ +u32 get_relay_feerate_floor(const struct chain_topology *topo UNNEEDED) +{ fprintf(stderr, "get_relay_feerate_floor called!\n"); abort(); } /* Generated stub for hsm_secret_arg */ char *hsm_secret_arg(const tal_t *ctx UNNEEDED, const char *arg UNNEEDED, diff --git a/tests/test_misc.py b/tests/test_misc.py index 399e5ee7744f..7998ee0692b7 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2343,9 +2343,9 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind, anchors): "perkb": { "opening": 30000, # This has increased (rounded up) - "mutual_close": 20004, + "mutual_close": 20176, # This has increased (rounded up) - "unilateral_anchor_close": 20004, + "unilateral_anchor_close": 20176, # This has increased (rounded up) "unilateral_close": 44000, "penalty": 30000, @@ -2364,14 +2364,14 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind, anchors): "feerate": 30000, "smoothed_feerate": 30000}, {"blockcount": 100, - "feerate": 20004, - "smoothed_feerate": 20004}], + "feerate": 20176, + "smoothed_feerate": 20176}], }, "onchain_fee_estimates": { "opening_channel_satoshis": 5265, # This increases too - "mutual_close_satoshis": 3365, - "unilateral_close_satoshis": 5561 if anchors else 6578, + "mutual_close_satoshis": 3394, + "unilateral_close_satoshis": 5608 if anchors else 6578, "unilateral_close_nonanchor_satoshis": 6578, "htlc_timeout_satoshis": 7293, "htlc_success_satoshis": 7733, @@ -2387,15 +2387,15 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind, anchors): assert l1.rpc.feerates('perkb') == { "perkb": { # This has increased (rounded up!) - "opening": 30004, + "opening": 30252, # This has increased (rounded up!) - "mutual_close": 30004, + "mutual_close": 30252, # This has increased (rounded up!) - "unilateral_anchor_close": 30004, + "unilateral_anchor_close": 30252, "unilateral_close": 44000, # This has increased (rounded up!) - "penalty": 30004, - "splice": 30024, + "penalty": 30252, + "splice": 30272, # This has increased (rounded up) "min_acceptable": 30004, "max_acceptable": 600000, @@ -2408,19 +2408,19 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind, anchors): "smoothed_feerate": 44000}, # This has increased (rounded up!) {"blockcount": 12, - "feerate": 30004, - "smoothed_feerate": 30004}, + "feerate": 30252, + "smoothed_feerate": 30252}, # This has increased (rounded up!) {"blockcount": 100, - "feerate": 30004, - "smoothed_feerate": 30004}], + "feerate": 30252, + "smoothed_feerate": 30252}], }, "onchain_fee_estimates": { - "opening_channel_satoshis": 5265, + "opening_channel_satoshis": 5309, # This increases too - "mutual_close_satoshis": 5048, + "mutual_close_satoshis": 5089, # This increases too (anchors uses min(100blocks,5 sat/vB)) - "unilateral_close_satoshis": 8341 if anchors else 6578, + "unilateral_close_satoshis": 8410 if anchors else 6578, "unilateral_close_nonanchor_satoshis": 6578, "htlc_timeout_satoshis": 7293, "htlc_success_satoshis": 7733, From 42c0d1485542967c11c0a5282f0ae9507d3cc39a Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sat, 24 Oct 2026 21:39:42 -0400 Subject: [PATCH 3/4] pytest: run test_penalty_htlc_min_feerate with raised minrelaytxfee --- tests/test_closing.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_closing.py b/tests/test_closing.py index 52aac82dc4ab..4a8c3ea2360a 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -640,15 +640,22 @@ def test_penalty_inhtlc(node_factory, bitcoind, executor, chainparams, anchors): check_utxos_channel(l2, [channel_id], expected_2, tags) +@pytest.mark.parametrize("minrelaytxfee", [None, 1500, 2000, 2500, 3500]) @pytest.mark.parametrize("anchors", [False, True]) -def test_penalty_htlc_min_feerate(node_factory, bitcoind, executor, chainparams, anchors): +def test_penalty_htlc_min_feerate(node_factory, bitcoind, executor, chainparams, anchors, minrelaytxfee): """As test_penalty_inhtlc, but with every feerate estimate at the floor: the penalty for the HTLC must still pay the minimum relay fee, so its - weight has to include the revocation pubkey in its witness.""" + weight has to include the revocation pubkey in its witness, and the + fee has to round up as bitcoind's does.""" if chainparams['elements'] and anchors: pytest.skip('elementsd anchors unsupported') + if minrelaytxfee: + bitcoind.stop() + bitcoind.cmd_line += ['-minrelaytxfee={:.8f}'.format(minrelaytxfee / 10**8)] + bitcoind.start() + opts = {'dev-disable-commit-after': 1, 'feerates': (253, 253, 253, 253)} if anchors is False: From 80c6bdccc21e9f561e00542ad5dcf9812bbbb279 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 17:42:56 -0400 Subject: [PATCH 4/4] pytest: check our delayed sweep relays with a raised minrelaytxfee --- tests/test_closing.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index 4a8c3ea2360a..b2681c136ec4 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -698,6 +698,35 @@ def test_penalty_htlc_min_feerate(node_factory, bitcoind, executor, chainparams, r'Resolved THEIR_REVOKED_UNILATERAL/THEIR_HTLC by our proposal OUR_PENALTY_TX']) +@pytest.mark.parametrize("anchors", [False, True]) +def test_onchain_delayed_sweep_min_feerate(node_factory, bitcoind, chainparams, anchors): + """Our sweep of a delayed output aims far out, so it pays the lowest + feerate: with bitcoind's minimum above 1 sat/vB, that still has to relay.""" + + if chainparams['elements'] and anchors: + pytest.skip('elementsd anchors unsupported') + + bitcoind.stop() + bitcoind.cmd_line += ['-minrelaytxfee=0.00002000'] + bitcoind.start() + + opts = {'feerates': (253, 253, 253, 253)} + if anchors is False: + opts['dev-force-features'] = "-23" + + l1, l2 = node_factory.line_graph(2, opts=opts) + l2.stop() + l1.rpc.close(l2.info['id'], 1) + bitcoind.generate_block(1, wait_for_mempool=1) + + _, txid, blocks = l1.wait_for_onchaind_tx('OUR_DELAYED_RETURN_TO_WALLET', + 'OUR_UNILATERAL/DELAYED_OUTPUT_TO_US') + bitcoind.generate_block(blocks) + l1.mine_txid_or_rbf(txid) + l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET') + assert not l1.daemon.is_in_log('min relay fee not met') + + @pytest.mark.parametrize("anchors", [False, True]) def test_penalty_outhtlc(node_factory, bitcoind, executor, chainparams, anchors): """Test penalty transaction with an outgoing HTLC"""