mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-16 13:00:59 +02:00
AskreneLayer: extract shared helpers for clboss layer writes
Move the askrene-inform-channel and askrene-disable-node helpers from Boss::Mod::FundsMover::Attempter::Impl (where they were private member functions) into a new shared module Boss::Mod::AskreneLayer. Lift the "clboss" layer name from a FundsMover-local constant into a shared constant. Pure refactor, no behavioral change. Existing tests still pass (79/79). Done now because PR4 (ActiveProber feeding the same layer) will be the second producer; sharing avoids duplicating the JSON construction, the layer-name string, and the silent-RpcError-catch logic. The new module exposes three free functions: - inform_channel_constrained(rpc, layer, scid, dir, amount): same shape and semantics as Attempter's prior private method. - inform_channel_succeeded(rpc, layer, scid, dir, amount): new variant emitting inform=succeeded instead of inform=constrained. Not used by FundsMover (sendpay failures only carry negative signals). Added in this commit because the implementation is one line over the shared internal helper, and PR4's open-question may have ActiveProber call it on probe-success to record positive lower-bound observations on probed channels. - disable_node(rpc, layer, node_id): same shape and semantics as Attempter's prior private method. All three functions silently swallow RpcError, matching the prior behavior in Attempter. The rationale: if the layer is unavailable (e.g. CLN < v24.11 where askrene layers do not exist), the call gracefully degrades to no-op rather than crashing the caller. Better degraded learning than crashed plugin. The layer name "clboss" follows the xpay convention: persistent shared-knowledge layer = owning plugin name. All CLBOSS-internal writers (FundsMover today, ActiveProber in PR4, possibly others later) target the same single layer so the accumulated knowledge benefits every downstream getroutes call.
This commit is contained in:
parent
96dbec2d7d
commit
17ba012f9b
5 changed files with 195 additions and 72 deletions
106
Boss/Mod/AskreneLayer.cpp
Normal file
106
Boss/Mod/AskreneLayer.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#include"Boss/Mod/AskreneLayer.hpp"
|
||||
#include"Boss/Mod/Rpc.hpp"
|
||||
#include"Ev/Io.hpp"
|
||||
#include"Jsmn/Object.hpp"
|
||||
#include"Json/Out.hpp"
|
||||
#include"Util/stringify.hpp"
|
||||
#include<assert.h>
|
||||
|
||||
namespace Boss { namespace Mod { namespace AskreneLayer {
|
||||
|
||||
std::string const clboss_layer_name = "clboss";
|
||||
|
||||
namespace {
|
||||
|
||||
/* Common machinery for the two inform_channel variants. askrene
|
||||
* accepts inform=succeeded / constrained / unconstrained as the
|
||||
* only difference between them; everything else (scid_dir,
|
||||
* amount_msat, layer) is identical.
|
||||
*/
|
||||
Ev::Io<void>
|
||||
inform_channel( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::Scid scid
|
||||
, std::uint32_t direction
|
||||
, Ln::Amount amount
|
||||
, char const* inform
|
||||
) {
|
||||
/* askrene only accepts direction 0 or 1 in
|
||||
* short_channel_id_dir. All callers feed values from
|
||||
* CLN's getroutes/sendpay responses, which are
|
||||
* guaranteed to be 0/1, but guard explicitly: a bad
|
||||
* direction would produce a syntactically valid but
|
||||
* semantically wrong RPC param that askrene rejects, and
|
||||
* the silent-swallow RpcError handler below would drop
|
||||
* the learning update without a trace.
|
||||
*/
|
||||
assert(direction <= 1);
|
||||
if (direction > 1)
|
||||
return Ev::lift();
|
||||
auto sdir = std::string(scid) + "/" + Util::stringify(direction);
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("layer", layer)
|
||||
.field("short_channel_id_dir", sdir)
|
||||
.field("amount_msat", amount.to_msat())
|
||||
.field("inform", std::string(inform))
|
||||
.end_object()
|
||||
;
|
||||
return rpc.command( "askrene-inform-channel"
|
||||
, std::move(parms)
|
||||
).then([](Jsmn::Object _) {
|
||||
return Ev::lift();
|
||||
}).catching<RpcError>([](RpcError const&) {
|
||||
/* Non-fatal -- if the layer is missing (e.g. layer-
|
||||
* create failed at startup on CLN < v24.11),
|
||||
* subsequent getroutes calls simply will not benefit
|
||||
* from the constraint. Better to degrade learning
|
||||
* than to crash the caller.
|
||||
*/
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ev::Io<void>
|
||||
inform_channel_constrained( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::Scid scid
|
||||
, std::uint32_t direction
|
||||
, Ln::Amount amount
|
||||
) {
|
||||
return inform_channel(rpc, layer, scid, direction, amount, "constrained");
|
||||
}
|
||||
|
||||
Ev::Io<void>
|
||||
inform_channel_unconstrained( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::Scid scid
|
||||
, std::uint32_t direction
|
||||
, Ln::Amount amount
|
||||
) {
|
||||
return inform_channel(rpc, layer, scid, direction, amount, "unconstrained");
|
||||
}
|
||||
|
||||
Ev::Io<void>
|
||||
disable_node( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::NodeId node
|
||||
) {
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("layer", layer)
|
||||
.field("node", std::string(node))
|
||||
.end_object()
|
||||
;
|
||||
return rpc.command( "askrene-disable-node"
|
||||
, std::move(parms)
|
||||
).then([](Jsmn::Object _) {
|
||||
return Ev::lift();
|
||||
}).catching<RpcError>([](RpcError const&) {
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
}}}
|
||||
74
Boss/Mod/AskreneLayer.hpp
Normal file
74
Boss/Mod/AskreneLayer.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef BOSS_MOD_ASKRENELAYER_HPP_
|
||||
#define BOSS_MOD_ASKRENELAYER_HPP_
|
||||
|
||||
#include"Ev/Io.hpp"
|
||||
#include"Ln/Amount.hpp"
|
||||
#include"Ln/NodeId.hpp"
|
||||
#include"Ln/Scid.hpp"
|
||||
#include<cstdint>
|
||||
#include<string>
|
||||
|
||||
namespace Boss { namespace Mod { class Rpc; } }
|
||||
|
||||
namespace Boss { namespace Mod { namespace AskreneLayer {
|
||||
|
||||
/* Name of the persistent askrene layer that CLBOSS subsystems
|
||||
* write failure-feedback and (optionally) success-observations
|
||||
* into. Following the xpay convention -- the layer is named
|
||||
* after the plugin that owns it. All CLBOSS writes go to this
|
||||
* layer; downstream getroutes calls that include it in their
|
||||
* layers array benefit from the accumulated knowledge.
|
||||
*/
|
||||
extern std::string const clboss_layer_name;
|
||||
|
||||
/* Tell askrene that a directed channel could not push at least
|
||||
* the given amount recently. Future getroutes calls that
|
||||
* include the clboss layer will treat this as an upper-bound
|
||||
* constraint and steer around it. Non-fatal on RpcError: if
|
||||
* the layer is missing (e.g. CLN < v24.11 where askrene layers
|
||||
* are unavailable) the call silently completes -- failure-mode
|
||||
* is degraded learning, not crashed caller.
|
||||
*/
|
||||
Ev::Io<void> inform_channel_constrained( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::Scid scid
|
||||
, std::uint32_t direction
|
||||
, Ln::Amount amount
|
||||
);
|
||||
|
||||
/* Tell askrene that a directed channel successfully pushed at
|
||||
* least the given amount recently. Future getroutes calls
|
||||
* that include the layer will treat this as a lower-bound on
|
||||
* the channel's capacity (min=amount, max=NULL). Non-fatal
|
||||
* on RpcError, same rationale as inform_channel_constrained.
|
||||
*
|
||||
* Naming note: this maps to askrene's `inform=unconstrained`
|
||||
* mode (which means "no upper-bound; minimum is amount").
|
||||
* Askrene also has an `inform=succeeded` mode but as of
|
||||
* v26.06 that branch is a no-op stub in askrene.c
|
||||
* (`FIXME: We could do something useful here!`). xpay uses
|
||||
* `inform=unconstrained` for the same after-success
|
||||
* lower-bound-raise pattern (see plugins/xpay/xpay.c
|
||||
* around `"We learned something about prior nodes"`).
|
||||
*/
|
||||
Ev::Io<void> inform_channel_unconstrained( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::Scid scid
|
||||
, std::uint32_t direction
|
||||
, Ln::Amount amount
|
||||
);
|
||||
|
||||
/* Tell askrene to avoid the given node entirely for routes
|
||||
* that include this layer. Used for NODE-level onion failures
|
||||
* (failcode bit 0x2000) and for unparsable-onion fallback,
|
||||
* where we cannot pin the failure to a specific channel.
|
||||
* Non-fatal on RpcError.
|
||||
*/
|
||||
Ev::Io<void> disable_node( Boss::Mod::Rpc& rpc
|
||||
, std::string const& layer
|
||||
, Ln::NodeId node
|
||||
);
|
||||
|
||||
}}}
|
||||
|
||||
#endif /* !defined(BOSS_MOD_ASKRENELAYER_HPP_) */
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include"Boss/Mod/AskreneLayer.hpp"
|
||||
#include"Boss/Mod/FundsMover/Attempter.hpp"
|
||||
#include"Boss/Mod/FundsMover/create_label.hpp"
|
||||
#include"Boss/Mod/Rpc.hpp"
|
||||
|
|
@ -16,22 +17,6 @@
|
|||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
namespace {
|
||||
|
||||
/* Name of the persistent askrene layer that holds CLBOSS's
|
||||
* failure-feedback knowledge -- channel capacity constraints
|
||||
* (askrene-inform-channel) and node-level exclusions
|
||||
* (askrene-disable-node) accumulated across attempts. Created
|
||||
* idempotently at startup by Boss::Mod::FundsMover::Main.
|
||||
*
|
||||
* Naming follows the convention established by plugins/xpay
|
||||
* (which uses "xpay"): the persistent shared-knowledge layer is
|
||||
* named after the plugin that owns it.
|
||||
*/
|
||||
auto const clboss_layer = std::string("clboss");
|
||||
|
||||
}
|
||||
|
||||
namespace Boss { namespace Mod { namespace FundsMover {
|
||||
|
||||
class Attempter::Impl : public std::enable_shared_from_this<Impl> {
|
||||
|
|
@ -175,7 +160,7 @@ private:
|
|||
.start_array("layers")
|
||||
.entry("auto.localchans")
|
||||
.entry("auto.sourcefree")
|
||||
.entry(clboss_layer)
|
||||
.entry(Boss::Mod::AskreneLayer::clboss_layer_name)
|
||||
.end_array()
|
||||
.field("maxfee_msat",
|
||||
route_maxfee.to_msat())
|
||||
|
|
@ -397,58 +382,6 @@ private:
|
|||
});
|
||||
}
|
||||
|
||||
/* Tell askrene about a failed-channel constraint so future
|
||||
* getroutes calls steer around it. Recorded into the
|
||||
* persistent "clboss" layer; benefits all subsequent attempts
|
||||
* and (when they migrate to consume the same layer) other
|
||||
* CLBOSS subsystems too.
|
||||
*/
|
||||
Ev::Io<void> inform_channel_constrained( Ln::Scid scid
|
||||
, int dir
|
||||
, Ln::Amount at
|
||||
) {
|
||||
auto sdir = std::string(scid) + "/" + Util::stringify(dir);
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("layer", clboss_layer)
|
||||
.field("short_channel_id_dir", sdir)
|
||||
.field("amount_msat", at.to_msat())
|
||||
.field("inform", "constrained")
|
||||
.end_object()
|
||||
;
|
||||
return rpc.command( "askrene-inform-channel"
|
||||
, std::move(parms)
|
||||
).then([](Jsmn::Object _) {
|
||||
return Ev::lift();
|
||||
}).catching<RpcError>([](RpcError const&) {
|
||||
/* Non-fatal -- if the layer is missing
|
||||
* (e.g. layer-create failed at startup on a
|
||||
* CLN < v24.11), subsequent getroutes calls
|
||||
* simply will not benefit from the constraint.
|
||||
*/
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
/* Tell askrene to avoid a node entirely; same persistent
|
||||
* layer. Used for NODE-level onion failures (failcode bit
|
||||
* 0x2000) and for unparsable-onion fallback.
|
||||
*/
|
||||
Ev::Io<void> disable_node(Ln::NodeId node) {
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("layer", clboss_layer)
|
||||
.field("node", std::string(node))
|
||||
.end_object()
|
||||
;
|
||||
return rpc.command( "askrene-disable-node"
|
||||
, std::move(parms)
|
||||
).then([](Jsmn::Object _) {
|
||||
return Ev::lift();
|
||||
}).catching<RpcError>([](RpcError const&) {
|
||||
return Ev::lift();
|
||||
});
|
||||
}
|
||||
|
||||
Ev::Io<void> sendpay() {
|
||||
auto payment_hash = std::make_shared<Sha256::Hash>();
|
||||
|
|
@ -612,7 +545,11 @@ private:
|
|||
;
|
||||
/* 0x2000 == NODE level error. */
|
||||
if ((fail & 0x2000))
|
||||
feedback = disable_node(enode);
|
||||
feedback = Boss::Mod::AskreneLayer::disable_node(
|
||||
rpc,
|
||||
Boss::Mod::AskreneLayer::clboss_layer_name,
|
||||
enode
|
||||
);
|
||||
else
|
||||
/* The amount to pass to askrene-inform-channel
|
||||
* is the HTLC amount attempted on the specific
|
||||
|
|
@ -623,7 +560,9 @@ private:
|
|||
* [1, route.size()] here and route[eidx - 1]
|
||||
* describes the failing channel.
|
||||
*/
|
||||
feedback = inform_channel_constrained(
|
||||
feedback = Boss::Mod::AskreneLayer::inform_channel_constrained(
|
||||
rpc,
|
||||
Boss::Mod::AskreneLayer::clboss_layer_name,
|
||||
echan, edir,
|
||||
route[eidx - 1].amount_msat
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include"Boss/Mod/AskreneLayer.hpp"
|
||||
#include"Boss/Mod/FundsMover/Claimer.hpp"
|
||||
#include"Boss/Mod/FundsMover/Main.hpp"
|
||||
#include"Boss/Mod/FundsMover/Runner.hpp"
|
||||
|
|
@ -133,7 +134,8 @@ private:
|
|||
return Ev::lift().then([this]() {
|
||||
auto parms = Json::Out()
|
||||
.start_object()
|
||||
.field("layer", "clboss")
|
||||
.field("layer",
|
||||
Boss::Mod::AskreneLayer::clboss_layer_name)
|
||||
.field("persistent", true)
|
||||
.end_object()
|
||||
;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ libclboss_la_SOURCES = \
|
|||
Boss/Mod/ActiveProber.hpp \
|
||||
Boss/Mod/AmountSettingsHandler.cpp \
|
||||
Boss/Mod/AmountSettingsHandler.hpp \
|
||||
Boss/Mod/AskreneLayer.cpp \
|
||||
Boss/Mod/AskreneLayer.hpp \
|
||||
Boss/Mod/AutoDisconnector.cpp \
|
||||
Boss/Mod/AutoDisconnector.hpp \
|
||||
Boss/Mod/AvailableRpcCommandsAnnouncer.cpp \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue