ChannelCreator: dowse to max-channel so new channels aren't pinned to min-channel
Some checks are pending
Code Base Sanity Check / tests (push) Waiting to run
Code Base Sanity Check / coverage (push) Waiting to run
Code Base Sanity Check / build-clang (push) Waiting to run

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.
This commit is contained in:
Ken Sedgwick 2026-06-13 12:25:33 -07:00
parent 5bdd1d609a
commit f345b16db1
No known key found for this signature in database
GPG key ID: DBD2AF0849D711A9
3 changed files with 41 additions and 33 deletions

View file

@ -101,14 +101,17 @@ Manager::on_request_channel_creation(Ln::Amount amt) {
auto amount = std::make_shared<Ln::Amount>();
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

View file

@ -33,12 +33,12 @@ Ev::Io<void> 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<Run>( bus, r.requester
, r.fromid, r.toid
, r.min_amount
, r.probe_target
);
return Ev::lift().then([this]() {
return wait_for_rpc(rpc);

View file

@ -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;
};
}}