clboss/Boss/Mod/SetConfigHandler.cpp

123 lines
3.8 KiB
C++
Raw Permalink Normal View History

Add dynamic (setconfig-tunable) plugin option infrastructure Introduces the mechanism for runtime-mutable plugin options: an option marked dynamic can be changed via `lightning-cli setconfig <name> <val>` without restarting clboss or lightningd. No option opts in yet -- this is the foundation (the rebalancer mode selector is the first consumer). - Boss::Msg::ManifestOption gains a bool dynamic field (default false, preserving the existing startup-only contract). - 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) from Msg::ManifestOption events, then handles incoming setconfig CommandRequests: it validates the named option is registered and dynamic, and re-raises a fresh Msg::Option on the bus, so existing option handlers re-apply the new value transparently. Because Msg::Option is now re-emitted at runtime (not only during init), subscribers must filter by name and tolerate post-init arrival. AmountSettingsHandler gains an `if (!settings) return` guard: it moves `settings` away at EndOfOptions, so a later Msg::Option for an unrelated name must be dropped -- this also fixes a latent assert(settings) crash that any post-EndOfOptions Msg::Option would have tripped. Contract documented in SetConfigHandler.hpp: lightningd delivers Int/Bool/Flag option values as JSON primitives at startup but as JSON strings at setconfig time, so dynamic-option handlers must accept both Jsmn shapes.
2026-06-08 12:13:55 -07:00
#include"Boss/Mod/SetConfigHandler.hpp"
#include"Boss/Msg/CommandFail.hpp"
#include"Boss/Msg/CommandRequest.hpp"
#include"Boss/Msg/CommandResponse.hpp"
#include"Boss/Msg/ManifestOption.hpp"
#include"Boss/Msg/Option.hpp"
#include"Boss/log.hpp"
#include"Ev/Io.hpp"
#include"Jsmn/Object.hpp"
#include"Json/Out.hpp"
#include"S/Bus.hpp"
#include<memory>
Add dynamic (setconfig-tunable) plugin option infrastructure Introduces the mechanism for runtime-mutable plugin options: an option marked dynamic can be changed via `lightning-cli setconfig <name> <val>` without restarting clboss or lightningd. No option opts in yet -- this is the foundation (the rebalancer mode selector is the first consumer). - Boss::Msg::ManifestOption gains a bool dynamic field (default false, preserving the existing startup-only contract). - 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) from Msg::ManifestOption events, then handles incoming setconfig CommandRequests: it validates the named option is registered and dynamic, and re-raises a fresh Msg::Option on the bus, so existing option handlers re-apply the new value transparently. Because Msg::Option is now re-emitted at runtime (not only during init), subscribers must filter by name and tolerate post-init arrival. AmountSettingsHandler gains an `if (!settings) return` guard: it moves `settings` away at EndOfOptions, so a later Msg::Option for an unrelated name must be dropped -- this also fixes a latent assert(settings) crash that any post-EndOfOptions Msg::Option would have tripped. Contract documented in SetConfigHandler.hpp: lightningd delivers Int/Bool/Flag option values as JSON primitives at startup but as JSON strings at setconfig time, so dynamic-option handlers must accept both Jsmn shapes.
2026-06-08 12:13:55 -07:00
namespace {
/* JSON-RPC error code for malformed parameters, matching the
* JSONRPC2 invalid-params constant used elsewhere in clboss. */
constexpr int RPC_INVALID_PARAMS = -32602;
}
namespace Boss { namespace Mod {
void SetConfigHandler::start() {
bus.subscribe<Boss::Msg::ManifestOption
>([this](Boss::Msg::ManifestOption const& o) {
options[o.name] = o.dynamic;
return Ev::lift();
});
bus.subscribe<Boss::Msg::CommandRequest
>([this](Boss::Msg::CommandRequest const& m) {
if (m.command != "setconfig")
return Ev::lift();
auto id = m.id;
auto const& params = m.params;
/* Extract `config` (required string). */
if (!params.is_object() || !params.has("config")
|| !params["config"].is_string()) {
return bus.raise(Boss::Msg::CommandFail{
id, RPC_INVALID_PARAMS,
"setconfig: missing or non-string 'config' "
"parameter",
Json::Out::empty_object()
});
}
auto name = std::string(params["config"]);
/* Verify the option is one we registered, and is
* declared dynamic. Lightningd should never forward
* setconfig for a non-dynamic option (libplugin would
* refuse it on the receiving side too), but defending
* here keeps the error surface clear and prevents a
* surprise Msg::Option re-raise for an option whose
* handler may not expect runtime updates. */
auto it = options.find(name);
if (it == options.end()) {
return bus.raise(Boss::Msg::CommandFail{
id, RPC_INVALID_PARAMS,
"setconfig: unknown option '" + name + "'",
Json::Out::empty_object()
});
}
if (!it->second) {
return bus.raise(Boss::Msg::CommandFail{
id, RPC_INVALID_PARAMS,
"setconfig: option '" + name
+ "' is not dynamic",
Json::Out::empty_object()
});
}
/* Forward the value as-is. Lightningd encodes the new
* value as a JSON string (see plugin_set_dynamic_opt in
* cln/lightningd/plugin.c), so handlers will see a
* Jsmn::Object with is_string() == true here even for
* numeric option types. The contract documented on
* SetConfigHandler covers this.
*
* Note: bus.raise(Msg::Option) broadcasts to every
* Msg::Option subscriber, not just the one that owns
* this option. Subscribers must filter by name and
* no-op on non-matches -- see the doc comment on
* Boss::Msg::Option for the full contract. */
auto value = params.has("val")
? params["val"]
: Jsmn::Object();
/* Rejection back-channel: bus.raise() completes only
* after every subscriber has run, so the owner's verdict
* is in reject_reason by the time we acknowledge.
* Failing the command matters beyond cosmetics:
* lightningd persists the new value (configvar_save)
* only on a success response, so a blanket ack would
* record values clboss never applied -- and a
* non-numeric value persisted for an int-typed option
* even fails lightningd's own option parse on the next
* start. */
auto reject_reason = std::make_shared<std::string>();
Add dynamic (setconfig-tunable) plugin option infrastructure Introduces the mechanism for runtime-mutable plugin options: an option marked dynamic can be changed via `lightning-cli setconfig <name> <val>` without restarting clboss or lightningd. No option opts in yet -- this is the foundation (the rebalancer mode selector is the first consumer). - Boss::Msg::ManifestOption gains a bool dynamic field (default false, preserving the existing startup-only contract). - 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) from Msg::ManifestOption events, then handles incoming setconfig CommandRequests: it validates the named option is registered and dynamic, and re-raises a fresh Msg::Option on the bus, so existing option handlers re-apply the new value transparently. Because Msg::Option is now re-emitted at runtime (not only during init), subscribers must filter by name and tolerate post-init arrival. AmountSettingsHandler gains an `if (!settings) return` guard: it moves `settings` away at EndOfOptions, so a later Msg::Option for an unrelated name must be dropped -- this also fixes a latent assert(settings) crash that any post-EndOfOptions Msg::Option would have tripped. Contract documented in SetConfigHandler.hpp: lightningd delivers Int/Bool/Flag option values as JSON primitives at startup but as JSON strings at setconfig time, so dynamic-option handlers must accept both Jsmn shapes.
2026-06-08 12:13:55 -07:00
return Boss::log( bus, Debug
, "SetConfigHandler: dispatching setconfig "
"'%s'"
, name.c_str()
)
+ bus.raise(Boss::Msg::Option{
name, std::move(value), reject_reason
})
+ Ev::lift().then([this, id, reject_reason]() {
if (!reject_reason->empty())
return bus.raise(Boss::Msg::CommandFail{
id, RPC_INVALID_PARAMS,
"setconfig: " + *reject_reason,
Json::Out::empty_object()
});
return bus.raise(Boss::Msg::CommandResponse{
Add dynamic (setconfig-tunable) plugin option infrastructure Introduces the mechanism for runtime-mutable plugin options: an option marked dynamic can be changed via `lightning-cli setconfig <name> <val>` without restarting clboss or lightningd. No option opts in yet -- this is the foundation (the rebalancer mode selector is the first consumer). - Boss::Msg::ManifestOption gains a bool dynamic field (default false, preserving the existing startup-only contract). - 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) from Msg::ManifestOption events, then handles incoming setconfig CommandRequests: it validates the named option is registered and dynamic, and re-raises a fresh Msg::Option on the bus, so existing option handlers re-apply the new value transparently. Because Msg::Option is now re-emitted at runtime (not only during init), subscribers must filter by name and tolerate post-init arrival. AmountSettingsHandler gains an `if (!settings) return` guard: it moves `settings` away at EndOfOptions, so a later Msg::Option for an unrelated name must be dropped -- this also fixes a latent assert(settings) crash that any post-EndOfOptions Msg::Option would have tripped. Contract documented in SetConfigHandler.hpp: lightningd delivers Int/Bool/Flag option values as JSON primitives at startup but as JSON strings at setconfig time, so dynamic-option handlers must accept both Jsmn shapes.
2026-06-08 12:13:55 -07:00
id, Json::Out::empty_object()
});
});
Add dynamic (setconfig-tunable) plugin option infrastructure Introduces the mechanism for runtime-mutable plugin options: an option marked dynamic can be changed via `lightning-cli setconfig <name> <val>` without restarting clboss or lightningd. No option opts in yet -- this is the foundation (the rebalancer mode selector is the first consumer). - Boss::Msg::ManifestOption gains a bool dynamic field (default false, preserving the existing startup-only contract). - 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) from Msg::ManifestOption events, then handles incoming setconfig CommandRequests: it validates the named option is registered and dynamic, and re-raises a fresh Msg::Option on the bus, so existing option handlers re-apply the new value transparently. Because Msg::Option is now re-emitted at runtime (not only during init), subscribers must filter by name and tolerate post-init arrival. AmountSettingsHandler gains an `if (!settings) return` guard: it moves `settings` away at EndOfOptions, so a later Msg::Option for an unrelated name must be dropped -- this also fixes a latent assert(settings) crash that any post-EndOfOptions Msg::Option would have tripped. Contract documented in SetConfigHandler.hpp: lightningd delivers Int/Bool/Flag option values as JSON primitives at startup but as JSON strings at setconfig time, so dynamic-option handlers must accept both Jsmn shapes.
2026-06-08 12:13:55 -07:00
});
}
}}