mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
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.
134 lines
3.3 KiB
C++
134 lines
3.3 KiB
C++
#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
|
|
)
|
|
.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();
|
|
});
|
|
}
|
|
|
|
}}
|