mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
FundsMover: gate rebalances below clboss-min-rebalance-ppm
Decline a rebalance whose fee budget, as ppm of the amount being moved, is below a configurable floor, before any work begins. When a RequestMoveFunds arrives with fee_budget/amount under the floor, FundsMover emits the zero ResponseMoveFunds that Runner::finish() would have produced after giving up, without creating a Runner, calling getroutes, or fanning out the split-retry cascade. This is the lever the classic rebalancer was missing. Over a week of production (2026-06-18 onward) only 78 FundsMover deliveries succeeded, and their effective feerate has a hard floor around 50 ppm: only ~8% cleared below 40 ppm, median about 150 ppm. Meanwhile the two highest-traffic drains (LNBiG, LQWD-England) get budgeted at ~32-39 ppm, below that floor, so they delivered nothing while firing ~1500 doomed JIT rebalances a day, each fanning out into hundreds of fast-fail / 204 / 205 sub-attempts that pinned askrene at 4/4 for hours. The gate refuses those moves at the source. The option is dynamic (setconfig-tunable) so the floor can be swept at runtime, modeled on clboss-classic-layer-age-secs. Default 50; set to 0 to disable and attempt every requested move. This supersedes the two earlier storm band-aids (the per-(source,sink) Unaffordable cache and the maxfee=0 fast-fail), which tried to make the doomed storm cheaper instead of stopping it: the cache suppressed only ~22% of moves (blind to the 204 failure mode, and its amount/ppm domination did not generalize across the varying request stream), and the fast-fail only trimmed the LNBiG maxfee=0 splits. The gate subsumes both by stopping sub-floor moves before they start.
This commit is contained in:
parent
cebe1f86b5
commit
31bbf093d7
2 changed files with 131 additions and 0 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include"Boss/Msg/OptionType.hpp"
|
||||
#include"Boss/Msg/ProvideDeletablePaymentLabelFilter.hpp"
|
||||
#include"Boss/Msg/RequestMoveFunds.hpp"
|
||||
#include"Boss/Msg/ResponseMoveFunds.hpp"
|
||||
#include"Boss/Msg/SolicitDeletablePaymentLabelFilter.hpp"
|
||||
#include"Boss/Msg/TimerRandomHourly.hpp"
|
||||
#include"Boss/concurrent.hpp"
|
||||
|
|
@ -55,6 +56,16 @@ private:
|
|||
* age_clboss_layer() for the aging mechanics and rationale. */
|
||||
std::uint64_t aging_window_secs = std::uint64_t(21600);
|
||||
|
||||
/* Minimum fee budget, as ppm of the moved amount, worth attempting
|
||||
* a rebalance at. A requested move whose fee_budget/amount is below
|
||||
* this is declined up front -- no Runner, no getroutes, no split-
|
||||
* retry storm -- because classic rebalances essentially never clear
|
||||
* below ~50 ppm: the high-traffic drains get budgeted near 35 ppm and
|
||||
* deliver nothing while saturating askrene. Dynamic via
|
||||
* clboss-min-rebalance-ppm; default 50. Set to 0 to disable the gate
|
||||
* and attempt every requested move. */
|
||||
std::uint64_t min_rebalance_ppm = std::uint64_t(50);
|
||||
|
||||
void start() {
|
||||
bus.subscribe<Msg::Init>([this](Msg::Init const& init) {
|
||||
rpc = &init.rpc;
|
||||
|
|
@ -81,6 +92,22 @@ private:
|
|||
"layer has the analogous "
|
||||
"clboss-xrebalance-age-secs.",
|
||||
/* dynamic = */ true
|
||||
})
|
||||
+ bus.raise(Msg::ManifestOption{
|
||||
"clboss-min-rebalance-ppm",
|
||||
Msg::OptionType_Int,
|
||||
Json::Out::direct(min_rebalance_ppm),
|
||||
"Minimum fee budget, in ppm of the moved amount, "
|
||||
"worth attempting a rebalance at. A move whose "
|
||||
"requested fee_budget/amount is below this is "
|
||||
"declined immediately -- no route solve, no "
|
||||
"split-retry storm -- because classic rebalances "
|
||||
"essentially never succeed below this rate. "
|
||||
"Dynamic: settable at runtime via `lightning-cli "
|
||||
"setconfig clboss-min-rebalance-ppm <ppm>`. "
|
||||
"Default 50; set to 0 to disable (attempt every "
|
||||
"requested move).",
|
||||
/* dynamic = */ true
|
||||
});
|
||||
});
|
||||
bus.subscribe<Msg::Option
|
||||
|
|
@ -130,6 +157,53 @@ private:
|
|||
, aging_window_secs
|
||||
);
|
||||
});
|
||||
bus.subscribe<Msg::Option
|
||||
>([this](Msg::Option const& o) {
|
||||
if (o.name != "clboss-min-rebalance-ppm")
|
||||
return Ev::lift();
|
||||
/* Number at startup, string via setconfig -- the same
|
||||
* dual encoding clboss-classic-layer-age-secs handles.
|
||||
* Signed so a negative value is rejected below rather
|
||||
* than wrapping to a huge unsigned. */
|
||||
long long ppm = 0;
|
||||
try {
|
||||
if (o.value.is_number()) {
|
||||
ppm = static_cast<long long>(double(o.value));
|
||||
} else if (o.value.is_string()) {
|
||||
ppm = std::stoll(std::string(o.value));
|
||||
} else {
|
||||
return Boss::log( bus, Warn
|
||||
, "FundsMover: clboss-min-"
|
||||
"rebalance-ppm: unsupported "
|
||||
"value type; keeping %"
|
||||
PRIu64 "."
|
||||
, min_rebalance_ppm
|
||||
);
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
return Boss::log( bus, Warn
|
||||
, "FundsMover: clboss-min-rebalance-"
|
||||
"ppm: parse error '%s'; keeping %"
|
||||
PRIu64 "."
|
||||
, e.what()
|
||||
, min_rebalance_ppm
|
||||
);
|
||||
}
|
||||
if (ppm < 0) {
|
||||
return Boss::log( bus, Warn
|
||||
, "FundsMover: clboss-min-rebalance-"
|
||||
"ppm: must be >= 0; keeping %"
|
||||
PRIu64 "."
|
||||
, min_rebalance_ppm
|
||||
);
|
||||
}
|
||||
min_rebalance_ppm = std::uint64_t(ppm);
|
||||
return Boss::log( bus, Info
|
||||
, "FundsMover: min rebalance budget = %"
|
||||
PRIu64 " ppm"
|
||||
, min_rebalance_ppm
|
||||
);
|
||||
});
|
||||
bus.subscribe<Msg::RequestMoveFunds
|
||||
>([this](Msg::RequestMoveFunds const& m) {
|
||||
auto msg = std::make_shared<Msg::RequestMoveFunds>(m);
|
||||
|
|
@ -166,6 +240,46 @@ private:
|
|||
"Contact " PACKAGE_BUGREPORT
|
||||
);
|
||||
}
|
||||
/* Decline a rebalance whose fee budget is below
|
||||
* clboss-min-rebalance-ppm: classic rebalances
|
||||
* essentially never clear below this rate, so skip
|
||||
* the whole Runner / getroutes / split-retry
|
||||
* machinery and emit the zero ResponseMoveFunds
|
||||
* that Runner::finish() would have produced after
|
||||
* giving up. Cross-multiplied to avoid a divide:
|
||||
* fee_budget/amount < min_ppm/1e6 iff
|
||||
* fee_budget*1e6 < min_ppm*amount. min_ppm == 0
|
||||
* disables the gate (the test is never true). */
|
||||
if ( min_rebalance_ppm > 0
|
||||
&& double(msg->fee_budget.to_msat()) * 1000000.0
|
||||
< double(min_rebalance_ppm)
|
||||
* double(msg->amount.to_msat())
|
||||
) {
|
||||
auto src_pfx =
|
||||
std::string(msg->source).substr(0, 8);
|
||||
auto dst_pfx =
|
||||
std::string(msg->destination).substr(0, 8);
|
||||
return Boss::log( bus, Debug
|
||||
, "FundsMover: not moving %s "
|
||||
"from %s... to %s... -- fee "
|
||||
"budget %s is below clboss-min-"
|
||||
"rebalance-ppm=%" PRIu64 "; "
|
||||
"never clears this cheap."
|
||||
, std::string(msg->amount).c_str()
|
||||
, src_pfx.c_str()
|
||||
, dst_pfx.c_str()
|
||||
, std::string(msg->fee_budget)
|
||||
.c_str()
|
||||
, min_rebalance_ppm
|
||||
)
|
||||
+ bus.raise(Msg::ResponseMoveFunds{
|
||||
msg->requester,
|
||||
Ln::Amount::sat(0),
|
||||
Ln::Amount::sat(0),
|
||||
msg->source,
|
||||
msg->destination
|
||||
});
|
||||
}
|
||||
auto runner = Runner::create( bus
|
||||
, *rpc
|
||||
, self_id
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -599,6 +599,23 @@ startup default, or at runtime with
|
|||
|
||||
lightning-cli setconfig clboss-classic-layer-age-secs <seconds>
|
||||
|
||||
### `--clboss-min-rebalance-ppm=<ppm>`
|
||||
|
||||
Sets the minimum fee budget, expressed as parts-per-million of the amount
|
||||
being moved, at which the classic rebalancer will bother attempting a
|
||||
rebalance. When a requested move's budget (`fee_budget / amount`) is below
|
||||
this, FundsMover declines it immediately — no route solve, no split-retry —
|
||||
rather than spending effort on a move that almost never succeeds at that
|
||||
rate.
|
||||
|
||||
The default is `50`. Set it to `0` to disable the gate and attempt every
|
||||
requested move.
|
||||
|
||||
This is a *dynamic* option: set it in the `lightningd` config for the
|
||||
startup default, or at runtime with
|
||||
|
||||
lightning-cli setconfig clboss-min-rebalance-ppm <ppm>
|
||||
|
||||
### `--clboss-min-nodes-to-process=<number>`
|
||||
|
||||
Sets the minimum number of nodes that CLBOSS must know about before it
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue