From 166b4efbafdb8a864ec5c3dc377815ce69647f61 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 4 Aug 2026 12:39:51 -0700 Subject: [PATCH] XRebalancer: request the full matched volume Remove clboss-xrebalance-size-factor and its per-cycle lo:hi random sweep. The matched-pool cycle now requests the full matched volume that cleared the floor. Both purposes in the option's help text no longer apply: the per-channel band-edge caps in the request lists bound what each channel absorbs, and per-request askrene layers removed the accumulated route state the size sweep kept from staling. The focused cycle was already sized flat (target deficit). The clboss-xrebalance-view emulation drops --size-factor to match. Configs that set the option must drop it before starting this build (an unknown plugin option fails lightningd startup). --- Boss/Mod/XRebalancer.cpp | 97 +++------------------------------- contrib/clboss-xrebalance-view | 52 +----------------- 2 files changed, 9 insertions(+), 140 deletions(-) diff --git a/Boss/Mod/XRebalancer.cpp b/Boss/Mod/XRebalancer.cpp index 3d5a3f9..78fc74d 100644 --- a/Boss/Mod/XRebalancer.cpp +++ b/Boss/Mod/XRebalancer.cpp @@ -36,7 +36,6 @@ namespace { auto const opt_per_hour = std::string("clboss-xrebalance-per-hour"); auto const opt_floor = std::string("clboss-xrebalance-route-cost-floor"); -auto const opt_size_factor = std::string("clboss-xrebalance-size-factor"); auto const opt_window_days = std::string("clboss-xrebalance-earnings-window-days"); auto const opt_fill_loc = std::string("clboss-xrebalance-fill-loc"); auto const opt_drain_loc = std::string("clboss-xrebalance-drain-loc"); @@ -47,7 +46,6 @@ auto const opt_gain = std::string("clboss-xrebalance-gain"); auto constexpr default_per_hour = double(12.0); auto constexpr default_floor = double(50.0); -auto constexpr default_size_factor = double(0.1); auto constexpr default_window_days = double(90.0); /* Tier bands (Loc%); match clboss-xrebalance-view defaults. */ auto constexpr default_fill_band = double(10.0); @@ -88,8 +86,6 @@ private: double per_hour; double floor_ppm; - double size_factor; /* lo bound, or the fixed value */ - double size_factor_hi; /* hi bound when size_factor is a lo:hi range */ double window_days; double fill_band; double drain_band; @@ -98,7 +94,6 @@ private: double grant_ppm; /* assumed prior rate (ppm of a capacity-turn) */ double gain; /* NetPpm multiplier */ bool floor_auto; /* floor option set to "auto" (sweep) */ - bool size_factor_range; /* size_factor set as lo:hi (per-cycle random) */ /* Mode xrebalance2: execute through the external xrebalance * plugin instead of clboss-xmovefunds. Captured at cycle start; * cycles are serialized by the awaited loop, so one flag @@ -129,8 +124,6 @@ private: void start() { per_hour = default_per_hour; floor_ppm = default_floor; - size_factor = default_size_factor; - size_factor_hi = default_size_factor; window_days = default_window_days; fill_band = default_fill_band; drain_band = default_drain_band; @@ -139,7 +132,6 @@ private: grant_ppm = default_grant; gain = default_gain; floor_auto = false; - size_factor_range = false; use_plugin = false; started = false; @@ -163,19 +155,6 @@ private: "amount and the maxfee budget. Or \"auto\": " "each cycle picks a random rung of the derived " "floor ladder (sweep).") - + manifest_option(opt_size_factor, default_size_factor, - "Multiplier (> 0) on the derived matched-pool " - "amount requested per cycle. <1 requests a " - "fraction (incremental fills); >1 over-fills " - "past the band targets -- recoverable, the " - "channel drifts back -- to make each of the " - "maxparts parts larger so it amortizes the base " - "fee and clears the per-part budget. Or a range " - "\"lo:hi\" (e.g. 0.5:3.0): each cycle draws a fresh " - "uniform-random multiplier in [lo,hi], sweeping the " - "request size so askrene's accumulated route state " - "does not stale into repeated 205/206 refusals the " - "way a single fixed value eventually does.") + manifest_option(opt_window_days, default_window_days, "Trailing window (days) over which per-channel " "NetPpm is measured for cycle selection.") @@ -203,7 +182,7 @@ private: "and price maxfee at the target's NetPpm plus " "the minimum NetPpm of the offered pool. The " "remaining cycles run the matched-pool style " - "(floor ladder / size-factor).") + "(floor ladder).") + manifest_option(opt_grant, default_grant, "Assumed prior earnings rate (ppm), credited " "to every channeled peer on both sides as if " @@ -266,48 +245,6 @@ private: } if (o.name == opt_floor) floor_auto = false; - /* size-factor also accepts a "lo:hi" range (e.g. 0.5:3.0): - * instead of one fixed multiplier, each cycle draws a fresh - * uniform-random value in [lo,hi] (see plan_and_log). Sweeping - * the size keeps the request varying so askrene's accumulated - * route state does not stale into repeated 205/206 refusals the - * way a fixed value eventually does. A plain number clears - * range mode and falls through to the numeric path below. */ - if (o.name == opt_size_factor) { - auto s = std::string(o.value); - auto colon = s.find(':'); - if (colon != std::string::npos) { - auto lo = double(0.0); - auto hi = double(0.0); - try { - lo = std::stod(s.substr(0, colon)); - hi = std::stod(s.substr(colon + 1)); - } catch (std::exception const&) { - o.reject(o.name + ": invalid range"); - return Boss::log( bus, Error - , "XRebalancer: ignoring invalid " - "%s range \"%s\"." - , o.name.c_str(), s.c_str() ); - } - if (!(lo > 0.0) || !(hi > 0.0)) { - o.reject( o.name + ": range bounds " - "must be > 0"); - return Boss::log( bus, Error - , "XRebalancer: %s range bounds " - "must be > 0; ignoring \"%s\"." - , o.name.c_str(), s.c_str() ); - } - if (hi < lo) { auto t = lo; lo = hi; hi = t; } - size_factor = lo; - size_factor_hi = hi; - size_factor_range = true; - return Boss::log( bus, Info - , "XRebalancer: %s set to range " - "%.3g:%.3g (per-cycle random)." - , o.name.c_str(), lo, hi ); - } - size_factor_range = false; - } /* maxparts is an integer count, not a continuous knob, so it * gets dedicated handling: parse, round, floor at 1 (askrene * requires >= 1), and store as an integer. */ @@ -339,7 +276,6 @@ private: double* target = nullptr; if (o.name == opt_per_hour) target = &per_hour; else if (o.name == opt_floor) target = &floor_ppm; - else if (o.name == opt_size_factor) target = &size_factor; else if (o.name == opt_window_days)target = &window_days; else if (o.name == opt_fill_loc) target = &fill_band; else if (o.name == opt_drain_loc) target = &drain_band; @@ -365,7 +301,7 @@ private: , o.name.c_str(), s.c_str() ); } - if (o.name == opt_size_factor || o.name == opt_gain) { + if (o.name == opt_gain) { if (!(v > 0.0)) { o.reject(o.name + ": must be > 0"); return Boss::log( bus, Error @@ -929,38 +865,21 @@ private: auto dest_caps = pick(fill); /* fill = where funds land */ auto source_caps = pick(drain); /* drain = where funds leave */ - /* size_factor may be a "lo:hi" range; draw a fresh multiplier - * each cycle so the requested size sweeps (see handle_option). */ - auto effective_size_factor = size_factor; - auto sf_note = std::string(); - if (size_factor_range) { - auto dist = std::uniform_real_distribution( - size_factor, size_factor_hi); - effective_size_factor = dist(Boss::random_engine); - auto os = std::ostringstream(); - os << " (rand " << size_factor << ":" << size_factor_hi << ")"; - sf_note = os.str(); - } - - auto requested = std::int64_t( - std::max(1.0, std::llround(double(best_n) - * effective_size_factor))); + /* Request the full matched volume; the per-channel caps in + * the request lists bound what each channel absorbs. */ + auto requested = best_n; auto maxfee = std::uint32_t(std::llround(best_joint)); return Boss::log( bus, Info, "%s", levels_str.c_str() ) + Boss::log( bus, Info , "XRebalancer: cycle [matched] floor=%.1f%s window=%.0fd " - "-> derived N=%s sat, joint=%.1f ppm " - "(fill>=%.1f + drain>=%.1f); size_factor=%.3g%s " - "-> request=%s sat (maxfee %u ppm); " + "-> request=%s sat (matched volume), joint=%.1f ppm " + "(fill>=%.1f + drain>=%.1f), maxfee %u ppm; " "sources=%zu dests=%zu; executing." , effective_floor, picked_note.c_str(), window_days , Util::Str::group_digits( - std::int64_t(best_n)).c_str(), best_joint + std::int64_t(requested)).c_str(), best_joint , best_fill_ppm, best_drain_ppm - , effective_size_factor, sf_note.c_str() - , Util::Str::group_digits( - std::int64_t(requested)).c_str() , (unsigned)maxfee , source_caps.size(), dest_caps.size() ).then([this, source_caps, dest_caps]() { diff --git a/contrib/clboss-xrebalance-view b/contrib/clboss-xrebalance-view index 8b4ada7..ea659c5 100755 --- a/contrib/clboss-xrebalance-view +++ b/contrib/clboss-xrebalance-view @@ -132,7 +132,6 @@ def read_live_config(lightning_dir, network_option): ("drain_loc", "clboss-xrebalance-drain-loc"), ("days", "clboss-xrebalance-earnings-window-days"), ("route_cost_floor", "clboss-xrebalance-route-cost-floor"), - ("size_factor", "clboss-xrebalance-size-factor"), ("maxparts", "clboss-xrebalance-maxparts"), ("grant", "clboss-xrebalance-grant"), ("gain", "clboss-xrebalance-gain"), @@ -412,19 +411,6 @@ def main(): "(a live value of \"auto\" means the driver sweeps a ladder; the " "full joint(N) staircase below shows every rung regardless)." ) - parser.add_argument( - "--size-factor", type=float, default=None, metavar="MULT", - help="Multiplier (> 0) on the derived matched-pool amount in the " - "emitted command. The full economic picture (bold set, " - "thresholds, budget) is still computed on the derived amount; " - "only the emitted amount_msat is scaled. <1 requests a fraction " - "(incremental -- the matched pool may pile into one endpoint, and " - "the rebalancer converges over many cycles). >1 over-fills past " - "the band targets (recoverable -- the channel drifts back) to " - "make each part larger so it amortizes the base fee. Default: " - "1.0." - ) - args = parser.parse_args() # Reconcile network option (lightning-cli wants 'bitcoin' for mainnet) @@ -511,31 +497,6 @@ def main(): args.route_cost_floor = 50.0 settings.append(("route-cost-floor", "50", "default")) - # --size-factor scales only the emitted amount. A live "lo:hi" is a - # per-cycle uniform sweep; emit at the mean (its expected value) and note - # the range. - if args.size_factor is not None: - settings.append(("size-factor", f"{args.size_factor:g}", "arg")) - elif live.get("size_factor") is not None \ - and ":" in str(live["size_factor"]): - lo, hi = (str(live["size_factor"]).split(":", 1) + [""])[:2] - lo, hi = _num(lo), _num(hi) - if lo is not None and hi is not None and lo > 0 and hi > 0: - args.size_factor = (lo + hi) / 2.0 - settings.append(("size-factor", - f"{args.size_factor:g} (live sweeps " - f"{live['size_factor']}; emitted at the mean)", - "live")) - else: - args.size_factor = 1.0 - settings.append(("size-factor", "1", "default")) - elif _num(live.get("size_factor")) is not None: - args.size_factor = _num(live["size_factor"]) - settings.append(("size-factor", f"{args.size_factor:g}", "live")) - else: - args.size_factor = 1.0 - settings.append(("size-factor", "1", "default")) - # grant / gain: strictness benders, same semantics as the driver's # clboss-xrebalance-grant / -gain. Overriding them on the command # line previews a combo before setconfig-ing it live. @@ -567,9 +528,6 @@ def main(): maxparts = int(_num(live["maxparts"])) settings.append(("maxparts", str(maxparts), "live")) - if not (args.size_factor > 0.0): - sys.exit("--size-factor must be > 0") - # The matched-pool cycle analysis (and its bands) always runs now, so # default to the tier sort that visualizes it: extreme bands ranked by # the relevant NetPpm (the In/OutNetPpm headers bold, and the most @@ -1441,15 +1399,7 @@ def main(): if r["scid"] in bold_scids for s in peer_scids[r["peer_id"]]] if source_picks and dest_picks: - # Scale the requested amount: the bold set / budget above - # reflect the full matched-pool economics; the emitted amount - # is scaled by size_factor (<1 incremental, >1 over-fills). - requested_sat = max(1, round(args.transfer_size - * args.size_factor)) - if args.size_factor != 1.0: - print(f"# size_factor {args.size_factor:g}: requesting " - f"{requested_sat:_} of {args.transfer_size:_} sat " - f"derived (budget unchanged at {int(joint)} ppm).") + requested_sat = max(1, round(args.transfer_size)) cmd = build_xmovefunds_command( network_option, lightning_dir, source_picks, dest_picks,