clboss/Boss/Mod/AmountSettingsHandler.cpp

254 lines
7.7 KiB
C++
Raw Permalink Normal View History

#include"Boss/Mod/AmountSettingsHandler.hpp"
#include"Boss/Msg/AmountSettings.hpp"
#include"Boss/Msg/EndOfOptions.hpp"
#include"Boss/Msg/ManifestOption.hpp"
#include"Boss/Msg/Manifestation.hpp"
#include"Boss/Msg/Option.hpp"
#include"Boss/log.hpp"
#include"Jsmn/Object.hpp"
#include"S/Bus.hpp"
#include"Util/make_unique.hpp"
#include<assert.h>
#include<sstream>
#include<string>
namespace {
/* Default channel boundaries. */
auto const default_min_channel = Ln::Amount::sat( 500000);
auto const default_max_channel = Ln::Amount::sat(16777215);
/* Default amount to always leave onchain for future
* channel management actions. */
auto const default_reserve = Ln::Amount::sat( 30000);
/* The absolute lowest min_channel setting. */
auto const min_min_channel = Ln::Amount::sat( 500000);
/* How much larger the channel-creation trigger should be over
* the min_channel. */
auto const trigger_factor = double(2.0);
/* How much to add to the channel-creation trigger above, to get
* the amount to leave after creation. */
auto const additional_remaining = Ln::Amount::sat(20000);
/* The ChannelCreator Planner requires
* min_channel + min_remaining <= max_channel
* where min_remaining = trigger_factor * min_channel
* + additional_remaining. This is the lowest max_channel that
* satisfies it at the lowest allowed min_channel; below this no
* valid min_channel exists. */
auto const min_usable_max_channel =
(1.0 + trigger_factor) * min_min_channel + additional_remaining;
Ln::Amount parse_sats(Jsmn::Object value) {
auto is = std::istringstream(std::string(value));
auto sats = std::uint64_t();
is >> sats;
return Ln::Amount::sat(sats);
}
}
namespace Boss { namespace Mod {
class AmountSettingsHandler::Impl {
private:
S::Bus& bus;
std::unique_ptr<Msg::AmountSettings> settings;
void start() {
settings->min_channel = default_min_channel;
settings->max_channel = default_max_channel;
settings->reserve = default_reserve;
bus.subscribe<Msg::Manifestation
>([this](Msg::Manifestation const& _) {
assert(settings);
return bus.raise(Msg::ManifestOption{
"clboss-min-onchain",
Msg::OptionType_String,
Json::Out::direct(default_reserve.to_sat()),
"Target to leave this number of satoshis "
"onchain, putting the rest into channels."
}) + bus.raise(Msg::ManifestOption{
"clboss-min-channel",
Msg::OptionType_String,
Json::Out::direct(default_min_channel.to_sat()),
"Minimum size of channels to make."
}) + bus.raise(Msg::ManifestOption{
"clboss-max-channel",
Msg::OptionType_String,
Json::Out::direct(default_max_channel.to_sat()),
"Maximum size of channels to make."
});
});
bus.subscribe<Msg::Option
>([this](Msg::Option o) {
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
/* Msg::Option was originally a startup-only signal
* (delivered once per registered option between
* Manifestation and EndOfOptions), and EndOfOptions
* moves `settings` away on line below. Now that
* SetConfigHandler can re-raise Msg::Option at
* runtime to deliver setconfig updates, this handler
* may receive events for unrelated names long after
* `settings` has been moved -- e.g. a runtime
* `setconfig` on any dynamic option triggers a
* Msg::Option that hits every subscriber.
*
* Drop those silently: none of the options this
* handler owns is registered dynamic, so by the time
* we see a post-EndOfOptions event it cannot
* legitimately apply. This also covers a latent
* assertion crash that pre-existed dynamic options
* (any post-EOO Msg::Option for any name would have
* tripped the old assert(settings) here). */
if (!settings)
return Ev::lift();
auto const& name = o.name;
if (name == "clboss-min-onchain") {
settings->reserve = parse_sats(o.value);
if (settings->reserve == default_reserve)
return Ev::lift();
return Boss::log( bus, Info
, "AmountSettingsHandler: "
"Onchain reserve set by "
"--clboss-min-onchain to "
"%s satoshis."
, std::string(o.value).c_str()
);
} else if (name == "clboss-min-channel") {
settings->min_channel = parse_sats(o.value);
if (settings->min_channel == default_min_channel)
return Ev::lift();
return Boss::log( bus, Info
, "AmountSettingsHandler: "
"Minimum channel size set by "
"--clboss-min-channel to "
"%s satoshis."
, std::string(o.value).c_str()
);
} else if (name == "clboss-max-channel") {
settings->max_channel = parse_sats(o.value);
if (settings->max_channel == default_max_channel)
return Ev::lift();
return Boss::log( bus, Info
, "AmountSettingsHandler: "
"Maximum channel size set by "
"--clboss-max-channel to "
"%s satoshis."
, std::string(o.value).c_str()
);
}
return Ev::lift();
});
bus.subscribe<Msg::EndOfOptions
>([this](Msg::EndOfOptions const& _) {
assert(settings);
auto act = Ev::lift();
/* Validate settings. */
if (settings->min_channel < min_min_channel) {
settings->min_channel = min_min_channel;
act += Boss::log( bus, Info
, "AmountSettingsHandler: "
"--clboss-min-channel too "
"low, forced to %u."
, (unsigned int)
min_min_channel.to_sat()
);
}
if (settings->max_channel < min_usable_max_channel) {
act += Boss::log( bus, Warn
, "AmountSettingsHandler: "
"--clboss-max-channel %u too "
"low for any allowed "
"--clboss-min-channel, "
"forced to %u."
, (unsigned int)
settings->max_channel.to_sat()
, (unsigned int)
min_usable_max_channel.to_sat()
);
settings->max_channel = min_usable_max_channel;
}
/* Compute the rest. */
settings->min_amount = trigger_factor
* settings->min_channel
;
settings->min_remaining = settings->min_amount
+ additional_remaining
;
/* The ChannelCreator Planner asserts
* min_channel + min_remaining <= max_channel
* at construction, so a violating config would
* abort on the first channel-creation run.
* max_channel is the knob that sets typical
* channel size: keep it, and lower min_channel
* to the largest value that fits. */
if ( settings->min_channel + settings->min_remaining
> settings->max_channel
) {
auto lowered = Ln::Amount::sat(
( settings->max_channel
- additional_remaining
).to_sat()
/ (std::uint64_t)(1.0 + trigger_factor)
);
act += Boss::log( bus, Warn
, "AmountSettingsHandler: "
"--clboss-min-channel %u "
"and --clboss-max-channel %u "
"conflict (max must be at "
"least %u), "
"--clboss-min-channel "
"forced to %u."
, (unsigned int)
settings->min_channel.to_sat()
, (unsigned int)
settings->max_channel.to_sat()
, (unsigned int)
( settings->min_channel
+ settings->min_remaining
).to_sat()
, (unsigned int)
lowered.to_sat()
);
settings->min_channel = lowered;
settings->min_amount = trigger_factor
* settings->min_channel
;
settings->min_remaining = settings->min_amount
+ additional_remaining
;
}
/* Grab the settings and send it. */
auto msg = std::move(settings);
return act + bus.raise(std::move(*msg));
});
}
public:
Impl() =delete;
Impl(Impl&&) =delete;
Impl(Impl const&) =delete;
explicit
Impl( S::Bus& bus_
) : bus(bus_)
, settings(Util::make_unique<Msg::AmountSettings>())
{ start(); }
};
AmountSettingsHandler::~AmountSettingsHandler() =default;
AmountSettingsHandler::AmountSettingsHandler(AmountSettingsHandler&&) =default;
AmountSettingsHandler::AmountSettingsHandler(S::Bus& bus) : pimpl(Util::make_unique<Impl>(bus)) {}
}}