clboss/Boss/Mod/RebalanceModeManager.cpp
Ken Sedgwick 189fef36eb
XRebalancer: xrebalance2 mode drives the external xrebalance plugin
A third clboss-rebalance-mode value, xrebalance2, runs the same
XRebalancer planner but executes cycles through the external
xrebalance plugin's RPC instead of the in-clboss clboss-xmovefunds
executor.  The plugin does the layer splitting on stock askrene and
owns constraint knowledge and failure feedback, so the in-clboss
layer machinery (including the predictor) stays idle in this mode.

The new XRebalancePartMonitor subscribes to the plugin's
xrebalance_part notifications and raises Msg::XRebalanceAttribution
for each completed part, so EarningsTracker accounts plugin-moved
funds regardless of which client initiated the transfer.
Attribution is notification-only: a part that reaches terminal
state while clboss is down goes unaccounted.

Subscribing to the topic is safe without the plugin loaded
(lightningd only warns about unknown notification topics), and a
cycle fired with the plugin missing logs one line and retries next
cycle.
2026-08-04 11:02:15 -07:00

132 lines
3.5 KiB
C++

#include"Boss/Mod/RebalanceModeManager.hpp"
#include"Boss/Msg/Manifestation.hpp"
#include"Boss/Msg/ManifestOption.hpp"
#include"Boss/Msg/Option.hpp"
#include"Boss/Msg/OptionType.hpp"
#include"Boss/Msg/ProvideStatus.hpp"
#include"Boss/Msg/RequestRebalanceMode.hpp"
#include"Boss/Msg/ResponseRebalanceMode.hpp"
#include"Boss/Msg/SolicitStatus.hpp"
#include"Boss/RebalanceMode.hpp"
#include"Boss/log.hpp"
#include"Ev/Io.hpp"
#include"Json/Out.hpp"
#include"S/Bus.hpp"
#include"Util/make_unique.hpp"
namespace {
/* Single dynamic option: the config file sets the startup default and
* `setconfig clboss-rebalance-mode <mode>` switches it at runtime. */
auto const option_name = std::string("clboss-rebalance-mode");
}
namespace Boss { namespace Mod {
class RebalanceModeManager::Impl {
private:
S::Bus& bus;
RebalanceMode mode;
void start() {
mode = default_rebalance_mode;
bus.subscribe<Msg::Manifestation
>([this](Msg::Manifestation const& _) {
return bus.raise(Msg::ManifestOption{
option_name,
Msg::OptionType_String,
Json::Out::direct(std::string(
rebalance_mode_to_string(
default_rebalance_mode
)
)),
"Rebalancer mode: \"classic\" (the original "
"rebalancer), \"xrebalance\" (the circular askrene "
"rebalancer, tuned by clboss-xrebalance-*), "
"\"xrebalance2\" (the same rebalancer executing "
"through the external xrebalance plugin, which "
"must be loaded into lightningd), or "
"\"off\" (disable rebalancing). "
" Set in the config for the startup default or "
"at runtime with "
"`setconfig clboss-rebalance-mode <mode>`.",
true /* dynamic: runtime-settable via setconfig */
});
});
bus.subscribe<Msg::Option
>([this](Msg::Option const& o) {
if (o.name != option_name)
return Ev::lift();
auto s = std::string(o.value);
auto m = RebalanceMode();
if (!rebalance_mode_from_string(s, m)) {
/* No double quotes in the message:
* lightningd forwards plugin setconfig
* errors as the raw JSON-escaped token
* (plugin_setconfig_done uses the wire
* bytes verbatim), so embedded quotes
* reach the user doubled-escaped. */
o.reject( option_name + ": unrecognized "
"value '" + s + "'");
return Boss::log( bus, Error
, "RebalanceModeManager: "
"ignoring unrecognized "
"%s value \"%s\"; "
"keeping \"%s\"."
, option_name.c_str()
, s.c_str()
, rebalance_mode_to_string(mode)
);
}
if (m == mode)
return Ev::lift();
mode = m;
return Boss::log( bus, Info
, "RebalanceModeManager: "
"mode set to \"%s\"."
, rebalance_mode_to_string(mode)
);
});
bus.subscribe<Msg::RequestRebalanceMode
>([this](Msg::RequestRebalanceMode const& m) {
return bus.raise(Msg::ResponseRebalanceMode{
m.requester, mode
});
});
bus.subscribe<Msg::SolicitStatus
>([this](Msg::SolicitStatus const& _) {
auto out = Json::Out();
out.start_object()
.field("mode", std::string(rebalance_mode_to_string(mode)))
.end_object()
;
return bus.raise(Msg::ProvideStatus{
"rebalance_mode", std::move(out)
});
});
}
public:
Impl() =delete;
Impl(Impl&&) =delete;
Impl(Impl const&) =delete;
explicit
Impl(S::Bus& bus_) : bus(bus_) {
start();
}
};
RebalanceModeManager::RebalanceModeManager(RebalanceModeManager&&) =default;
RebalanceModeManager::~RebalanceModeManager() =default;
RebalanceModeManager::RebalanceModeManager(S::Bus& bus)
: pimpl(Util::make_unique<Impl>(bus)) { }
}}