clboss/Boss/Mod/Manifester.cpp

135 lines
3.3 KiB
C++
Raw Normal View History

#include"Boss/Mod/Manifester.hpp"
#include"Boss/Msg/CommandRequest.hpp"
#include"Boss/Msg/CommandResponse.hpp"
#include"Boss/Msg/ManifestCommand.hpp"
#include"Boss/Msg/ManifestHook.hpp"
#include"Boss/Msg/ManifestNotification.hpp"
#include"Boss/Msg/ManifestOption.hpp"
#include"Boss/Msg/Manifestation.hpp"
#include"Ev/Io.hpp"
#include"S/Bus.hpp"
#include<stdlib.h>
namespace {
std::string option_type_name(Boss::Msg::OptionType t) {
switch (t) {
case Boss::Msg::OptionType_String: return "string";
case Boss::Msg::OptionType_Bool: return "bool";
case Boss::Msg::OptionType_Int: return "int";
case Boss::Msg::OptionType_Flag: return "flag";
}
abort();
}
}
namespace Boss { namespace Mod {
void Manifester::start() {
bus.subscribe<Boss::Msg::CommandRequest>([this](Boss::Msg::CommandRequest const& req) {
if (req.command != "getmanifest")
return Ev::lift();
auto id = req.id;
return Ev::lift().then([this]() {
return bus.raise(Boss::Msg::Manifestation());
}).then([this, id]() {
/* At this point, other modules should have
* emitted the Manifest registration. */
auto result = Json::Out();
auto robj = result.start_object();
robj
.field("dynamic", true)
;
/* Holy carp.
* The C-lightning document describes `"features"`,
* but the actual field is named `"featurebits"`. */
auto harr = robj.start_array("hooks");
for (auto const& h : hooks)
harr.entry(h);
harr.end_array();
auto narr = robj.start_array("subscriptions");
for (auto const& n : notifications)
narr.entry(n);
narr.end_array();
auto carr = robj.start_array("rpcmethods");
for (auto const& c : commands) {
auto const& info = c.second;
carr.entry(
Json::Out()
.start_object()
.field("name", info.name)
.field("usage", info.usage)
.field("description", info.description)
.field("deprecated", info.deprecated)
.end_object()
);
}
carr.end_array();
auto oarr = robj.start_array("options");
for (auto const& n_o : options) {
auto const& o = n_o.second;
oarr.entry(
Json::Out()
.start_object()
.field("name", o.name)
.field("type"
, option_type_name(
o.type
)
)
.field( "default"
, o.default_value
)
.field( "description"
, o.description
)
XMoveFunds: age the clboss-xrebalance layer, runtime-tunable via setconfig Adds periodic askrene-age against the persistent clboss-xrebalance layer so capacity constraints written by inform_channel_constrained do not accumulate forever. Mirrors FundsMover's age_clboss_layer: fires on Msg::TimerRandomHourly, logs num_removed at Debug, and catches RpcError with -32601 stayed at Debug (graceful degradation on CLN without askrene-age) while other codes promote to Warn so sustained aging failure is visible. The aging cutoff is controlled by a new plugin option, clboss-xrebalance-age-secs, default 3600 (1h) to match FundsMover's production value. Operators on networks with slower flows (signet) are expected to widen this; the right value is empirical and will be tuned after observation. The option is registered dynamic=true so the window is mutable at runtime via: lightning-cli setconfig clboss-xrebalance-age-secs <secs> No clboss / lightningd restart required. To support that, three small infrastructure pieces: * Boss::Msg::ManifestOption gains a bool dynamic field (default false; preserves existing behavior). * Boss::Mod::Manifester emits the per-option dynamic flag in the getmanifest response, so lightningd knows to forward setconfig for that option. * New Boss::Mod::SetConfigHandler module records (name -> dynamic flag) from Msg::ManifestOption events, then handles incoming Msg::CommandRequest where command == "setconfig" by validating the named option is registered + dynamic and re-raising a fresh Msg::Option on the bus. Existing option handlers re-apply the new value transparently. Contract for any future opt-in to dynamic: at startup lightningd encodes Int / Bool / Flag option values as JSON primitives, but at setconfig time it encodes them as JSON strings. Handlers for dynamic options must accept both Jsmn shapes. The XMoveFunds option handler does this; the contract is documented in SetConfigHandler's header comment so future modules can opt in safely.
2026-06-01 09:31:50 -07:00
.field( "dynamic"
, o.dynamic
)
.end_object()
);
}
oarr.end_array();
robj.end_object();
/* Now the manifester has created the result,
* we can clear the objects.
*/
hooks.clear();
notifications.clear();
commands.clear();
return bus.raise(Boss::Msg::CommandResponse{
id, result
});
});
});
/* Registrations. */
bus.subscribe<Boss::Msg::ManifestCommand>([this](Boss::Msg::ManifestCommand const& c) {
commands[c.name] = c;
return Ev::lift();
});
bus.subscribe<Boss::Msg::ManifestHook>([this](Boss::Msg::ManifestHook const& h) {
hooks.insert(h.name);
return Ev::lift();
});
bus.subscribe<Boss::Msg::ManifestNotification>([this](Boss::Msg::ManifestNotification const& n) {
notifications.insert(n.name);
return Ev::lift();
});
bus.subscribe<Boss::Msg::ManifestOption>([this](Boss::Msg::ManifestOption const& o) {
options[o.name] = o;
return Ev::lift();
});
}
}}