From f345b16db14019799bf267c9d7ea6d2c9bb23d72 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Sat, 13 Jun 2026 12:25:33 -0700 Subject: [PATCH] ChannelCreator: dowse to max-channel so new channels aren't pinned to min-channel After the askrene dowser rewrite, the dowser caps its result at the amount it probes. The ChannelCreator's dowser probed at clboss-min-channel, but the Planner opens UP TO the dowsed flow (rejecting below min-channel, capping at max-channel), so every new channel was sized to exactly min-channel regardless of the candidate's real capacity. RearrangerBySize, which orders candidates by dowsed size, was blinded the same way. Observed on prod1 (min-channel 1M, max-channel 5M): four channels opened at exactly 1,000,000 sat. The min-channel probe is correct for the threshold callers -- the Janitor and Preinvestigator only ask 'is at least min-channel reachable?' -- but wrong for the ChannelCreator, the one caller that SIZES the open from the result. Probe it at max-channel instead, so a well-connected candidate reports its true reachable flow up to the largest channel we would open and the Planner can size anywhere in [min-channel, max-channel]. Rename Msg::RequestDowser::min_amount to probe_target: the field is the flow level the caller wants the probe sized to, not a minimum -- the ChannelCreator now passes max-channel, the threshold callers pass min-channel. --- Boss/Mod/ChannelCreator/Manager.cpp | 17 ++++++++------ Boss/Mod/Dowser.cpp | 36 ++++++++++++++--------------- Boss/Msg/RequestDowser.hpp | 21 ++++++++++------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/Boss/Mod/ChannelCreator/Manager.cpp b/Boss/Mod/ChannelCreator/Manager.cpp index 7320a9b..015a7ac 100644 --- a/Boss/Mod/ChannelCreator/Manager.cpp +++ b/Boss/Mod/ChannelCreator/Manager.cpp @@ -101,14 +101,17 @@ Manager::on_request_channel_creation(Ln::Amount amt) { auto amount = std::make_shared(); return Ev::lift().then([this, proposal, patron]() { - /* Size the probe to min_amount (clboss-min-channel) - * so a reachable candidate clears the Planner's - * `amt < min_amount` filter; the askrene dowser caps - * its result at the probe amount, so the old fixed - * 1M-sat probe rejected every candidate whenever - * min-channel was set at/above ~985k sat. */ + /* Size the probe to max_amount (clboss-max-channel), + * NOT min_amount: the Planner opens up to the dowsed + * flow (rejecting below min_amount, capping at + * max_amount), and the askrene dowser caps its result + * at the probe -- so probing at min_amount would pin + * every new channel to min-channel regardless of the + * candidate's real capacity. Probing at max_amount + * lets a well-connected candidate report its true + * reachable flow up to the largest channel we'd open. */ return dowser.execute(Msg::RequestDowser{ - nullptr, proposal, patron, min_amount + nullptr, proposal, patron, max_amount }); }).then([this , amount diff --git a/Boss/Mod/Dowser.cpp b/Boss/Mod/Dowser.cpp index ef43caa..1e92e48 100644 --- a/Boss/Mod/Dowser.cpp +++ b/Boss/Mod/Dowser.cpp @@ -33,12 +33,12 @@ Ev::Io wait_for_rpc(Boss::Mod::Rpc*& rpc) { } /* Default probe amount, used only when the request does not specify a - * min_amount (e.g. the manual clboss-dowser command). askrene caps the + * probe_target (e.g. the manual clboss-dowser command). askrene caps the * dowsed flow at whatever we probe, so this is also the ceiling on the * reported capacity -- a caller comparing the result against a threshold - * above this would always see a failing result. Threshold callers - * therefore pass their min_amount and we size the probe from it (see - * effective_probe_amount); this constant is the fallback only. */ + * above this would always see a failing result. Callers therefore pass + * their probe_target and we size the probe from it; this constant is the + * fallback only. */ auto const default_probe_amount = Ln::Amount::sat(1000000); /* Maximum number of paths askrene may use to split the probe across. @@ -87,26 +87,26 @@ public: , void* requester_ , Ln::NodeId const& fromid_ , Ln::NodeId const& toid_ - , Ln::Amount min_amount_ + , Ln::Amount probe_target_ ) : bus(bus_) , requester(requester_) , fromid(fromid_) , toid(toid_) , amount(Ln::Amount::sat(0)) /* Size the probe so a full-flow result, after the - * reserve_factor haircut, still clears the caller's - * min_amount threshold. In exact arithmetic - * (min/reserve)*reserve == min, but both the division and the - * haircut floor to integer msat, so the round-trip lands ~1 msat - * below min and the caller's `< min` check rejects every - * candidate. Add a 1-sat cushion -- larger than the worst-case - * double-floor loss, negligible against a >= min_amount channel - * -- so a full-flow result reports >= min. askrene caps the + * reserve_factor haircut, still reaches the caller's + * probe_target. In exact arithmetic (t/reserve)*reserve == t, + * but both the division and the haircut floor to integer msat, + * so the round-trip lands ~1 msat below t and a caller's + * `< target` check would reject a candidate that exactly meets + * it. Add a 1-sat cushion -- larger than the worst-case + * double-floor loss, negligible against a >= probe_target channel + * -- so a full-flow result reports >= target. askrene caps the * dowsed flow at the probe amount, so a probe smaller than the - * threshold could never produce a passing result. When no - * min_amount is given, fall back to the fixed default. */ - , probe_amount( min_amount_ > Ln::Amount::sat(0) - ? min_amount_ * (1.0 / reserve_factor) + Ln::Amount::sat(1) + * target could never produce a passing result. When no + * probe_target is given, fall back to the fixed default. */ + , probe_amount( probe_target_ > Ln::Amount::sat(0) + ? probe_target_ * (1.0 / reserve_factor) + Ln::Amount::sat(1) : default_probe_amount ) { } @@ -266,7 +266,7 @@ void Dowser::start() { >([this](Msg::RequestDowser const& r) { auto run = std::make_shared( bus, r.requester , r.fromid, r.toid - , r.min_amount + , r.probe_target ); return Ev::lift().then([this]() { return wait_for_rpc(rpc); diff --git a/Boss/Msg/RequestDowser.hpp b/Boss/Msg/RequestDowser.hpp index e3d66a6..fc8138d 100644 --- a/Boss/Msg/RequestDowser.hpp +++ b/Boss/Msg/RequestDowser.hpp @@ -18,14 +18,19 @@ struct RequestDowser { void* requester; Ln::NodeId fromid; Ln::NodeId toid; - /* The capacity the caller cares about: the dowser sizes its - * probe so that a full-flow result clears this threshold. When - * zero (the default), the dowser uses its own fixed default probe - * amount. Callers that test the result against clboss-min-channel - * MUST set this to that minimum -- the probe is capped at the - * amount requested, so a probe smaller than the threshold can - * never produce a passing result. */ - Ln::Amount min_amount; + /* The flow level the caller wants the probe sized to. The dowser + * sizes its probe so that a full-flow result reaches this amount; + * the askrene dowser caps its result at the probe, so it can never + * report more than this. When zero (the default), the dowser uses + * its own fixed default probe amount. + * + * Threshold callers that only ask "is at least clboss-min-channel + * reachable?" set this to min-channel. Callers that SIZE something + * from the result -- the ChannelCreator opens up to the dowsed + * amount -- must set this to the largest amount they would use + * (clboss-max-channel); otherwise the result is pinned to the probe + * and the size collapses to it. */ + Ln::Amount probe_target; }; }}