Commit graph

4 commits

Author SHA1 Message Date
Ken Sedgwick
41f2bbcc5d
Add the xrebalance track to the rebalance-mode and askrene-layer infra
Re-introduces the second rebalancing track (Track B, "xrebalance": the
circular askrene min-cost-flow rebalancer) into the shared selection and
layer infrastructure, so it can coexist with the classic track in one
binary and be chosen at runtime.

  - Boss::RebalanceMode gains the xrebalance value (+ to_string /
    from_string arms and the doc bullet), selectable via
    clboss-rebalance-mode alongside classic and off.
  - Boss::Mod::AskreneLayer gains xrebalance_layer_name
    ("clboss-xrebalance"), the persistent askrene layer the xrebalance
    code paths accumulate probe knowledge into.  Kept distinct from the
    "clboss" layer so the two tracks' learning does not commingle while
    both run side by side.
  - clboss-rebalance-mode help text now lists xrebalance.

This is only the seam: nothing drives xrebalance yet.  The XMoveFunds
primitive and the XRebalancer driver land in following commits.  The
classic rebalancers already gate on mode == classic, so selecting
xrebalance simply quiesces them (like off) until the driver arrives.
2026-08-04 11:01:57 -07:00
Ken Sedgwick
a2296177ba
Dowser, ActiveProber: restore self-exclusion via a shared clboss-self layer
The getroute -> getroutes migrations dropped the legacy
exclude=[self_id] argument in the two probing modules whose askrene
source is a remote node.  Nothing replaced it: askrene's gossmap
includes our public channels like anyone else's, and an empty layers
array applies no exclusions.  Consequences:

- Dowser capacity probes could route part of the candidate->patron
  flow through our own node, counting our own liquidity toward a
  candidate's capacity -- retaining weak candidates and over-sizing
  the channels ChannelCreator opens.

- ActiveProber probes could pick path[0] = peer->us, degenerating
  into a circular us->peer->us payment that measures our own shared
  channel's peer->us balance instead of the peer's outward reach,
  with SendpayResultMonitor crediting the peer destination_reached
  for it.

Introduce AskreneLayer::self_layer_name ("clboss-self"): a tiny
persistent layer whose only content is our node in disabled_nodes,
maintained by AskreneLayer::ensure_self_layer() (idempotent create,
deduped disable).  Both modules resolve it before probing and name it
in their getroutes layers array; when askrene is unavailable they
probe without it, as before.  Kept separate from the clboss layer --
whose disabled_nodes also carries self -- because that layer's
learned constraints would bias what the probes measure.
2026-08-04 11:01:55 -07:00
Ken Sedgwick
f48c7e9669
AskreneLayer: coalesce redundant inform-channel writes per channel-direction
askrene's get_constraints folds every constraint for a (layer, scid-dir) down
to a single tightest [min, max] at query time, so the per-HTLC stream of
inform-channel writes only bloats the layer. Hot rebalance corridors accreted
hundreds-to-thousands of dominated min_msat copies -- one survey found a single
channel-direction holding 5026 constraints -- every one of which askrene must
re-fold on each getroutes through that dir.

Coalesce at the inform_channel chokepoint: per (layer, scid-dir, inform-kind)
keep the tightest bound emitted in the current time bucket and write through
only on a new bucket (a keep-alive against the layer aging) or a tightening.
Dropping a dominated write is lossless -- it is exactly the entry
get_constraints discards when it folds. On the surveyed hot set this is a ~42x
depth reduction.

The bucket length is a fixed fraction (1/12) of the layer aging window
(clboss-classic-layer-age-secs), so it always stays well under the aging window
-- the once-per-bucket keep-alive refreshes a constraint before it can age out
-- and the aging window is then always exactly 12 buckets whatever its value,
making the prune and the depth floor scale-invariant. 30 minutes at the default
6h aging. FundsMover feeds the live value through set_aging_window_secs from its
clboss-classic-layer-age-secs handler, so a setconfig retunes the bucket
immediately.

A small amortized prune drops cache entries idle past the aging window so the
cache does not grow with the set of channel-directions seen over the process
lifetime. inform_coalesce_emit is factored out as a pure decision; the test
covers the dominance and oscillation cases plus a behavioural drop test.
2026-08-04 11:01:50 -07:00
Ken Sedgwick
2115cab12f
AskreneLayer: new shared module for clboss askrene-layer writes
Introduces Boss::Mod::AskreneLayer, a small shared module of free
functions that wrap the askrene layer-mutation RPCs against a named
layer.  This is the common infrastructure that the classic rebalancer
(FundsMover) and ActiveProber write their routing feedback into; both
producers land in following commits.

The layer name follows the xpay convention -- the persistent
shared-knowledge layer is named after the owning plugin.
clboss_layer_name = "clboss"; every CLBOSS writer targets this single
layer so downstream getroutes calls that include it in their layers
array benefit from the accumulated knowledge.

Functions:

  - inform_channel_constrained / inform_channel_unconstrained:
    askrene-inform-channel with inform=constrained / =unconstrained,
    recording a recent could-not-push upper bound on a directed
    channel, or clearing it.
  - disable_node: askrene-disable-node, steering routes away from a
    node.
  - is_node_disabled: reads askrene-listlayers to test whether a node
    is already disabled in the layer (lets writers dedup).
  - update_channel: askrene-update-channel, applying an xpay-style
    channel_update refresh.

All writers silently swallow RpcError: if the layer is unavailable
(e.g. CLN without askrene layers) the call degrades to a no-op rather
than crashing the caller -- degraded learning beats a crashed plugin.

A unit test (tests/boss/test_askrene_layer) covers each helper's method
and JSON shape plus the silent-RpcError-swallow contract, using the
socketpair MockRpcServer pattern.
2026-08-04 11:01:43 -07:00