mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-19 13:18:20 +02:00
Flips clboss-xmovefunds from plan-only to actually sending the
returned route via sendpay when execute=true. All the masking
and getroutes work from the previous commit is unchanged; this
commit adds the post-getroutes sendpay/waitsendpay machinery
plus the supporting infrastructure for self-payment HTLC
resolution.
Mechanism
Once getroutes returns one or more routes (potentially multi-
part), do_execute:
1. Generates a fresh preimage and payment_secret via the new
XMoveFunds::Claimer (mirroring FundsMover::Claimer's
pattern but maintained independently so the two
subsystems coexist without sharing the entry table). The
preimage is registered in the claim table so that when
the resulting HTLCs arrive at us we auto-resolve them.
2. Constructs a sendpay-format route per part by copying the
askrene path[] hops verbatim (mapping
short_channel_id_dir, amount_out_msat, cltv_out, and
node_id_out into the sendpay hop shape) and appending a
closing hop (fill_peer -> self_id via the dest_scid that
matches the route's last node_id_out).
3. Issues sendpay for each part with shared payment_hash,
payment_secret, label, and groupid. Single-part
payments use partid=0 (non-MPP). Multi-part payments use
partid 1..N and pass amount_msat as the total across all
parts.
4. Waits for every part via waitsendpay. Sendpay or
waitsendpay errors per part are captured into an errors
array so the overall reply can still summarise what
happened to each part instead of bailing on the first
failure.
Label format is "clboss-xrebalance-<unix-ts>" so the eventual
EarningsTracker integration can disambiguate xrebalance-family
payments from FundsMover ones (see R4 in the plan).
execute default flipped to true
Per the plan's "Manual command actually executes (signet)"
decision, the default is now execute=true. Caller explicitly
passes execute=false to get the plan-only response (the
existing "predict-and-compare" mode), which still returns the
askrene plan with no sendpay.
API surface
The reply gains:
status "executed" when sendpay+waitsendpay ran,
"execute_skipped" when execute=true was
requested but getroutes errored, or
"planned" when execute=false.
execution object, only present when status =
"executed":
payment_hash hex
preimage hex
label "clboss-xrebalance-<ts>"
groupid u64
parts number of MPP parts
results array of per-part waitsendpay responses
errors array of per-part error strings
(only present if any failed)
104 lines
2.9 KiB
C++
104 lines
2.9 KiB
C++
#include"Boss/Mod/XMoveFunds/Claimer.hpp"
|
|
#include"Boss/Msg/ProvideHtlcAcceptedDeferrer.hpp"
|
|
#include"Boss/Msg/ReleaseHtlcAccepted.hpp"
|
|
#include"Boss/Msg/SolicitHtlcAcceptedDeferrer.hpp"
|
|
#include"Boss/Msg/TimerRandomHourly.hpp"
|
|
#include"Boss/concurrent.hpp"
|
|
#include"Ev/Io.hpp"
|
|
#include"Ev/now.hpp"
|
|
#include"Ln/HtlcAccepted.hpp"
|
|
#include"S/Bus.hpp"
|
|
|
|
namespace {
|
|
|
|
/* Self-payment claim entries are kept for 24 hours. Anything
|
|
* older than that almost certainly belongs to a stuck or
|
|
* abandoned request and is safe to drop -- the in-flight HTLCs
|
|
* will time out via the on-chain mechanism. */
|
|
auto const timeout = double(3600 * 24);
|
|
|
|
}
|
|
|
|
namespace Boss { namespace Mod { namespace XMoveFunds {
|
|
|
|
void Claimer::start() {
|
|
bus.subscribe<Msg::SolicitHtlcAcceptedDeferrer
|
|
>([this](Msg::SolicitHtlcAcceptedDeferrer const&) {
|
|
auto f = [this](Ln::HtlcAccepted::Request const& r) {
|
|
auto const& h = r.payment_hash;
|
|
auto it = entries.find(h);
|
|
if (it == entries.end())
|
|
return Ev::lift(false);
|
|
auto const& payment_secret =
|
|
it->second.payment_secret;
|
|
|
|
if (r.payment_secret != payment_secret)
|
|
return Ev::lift(false);
|
|
|
|
/* Extract data. Note: we COPY the preimage and
|
|
* do NOT erase the entry, unlike FundsMover's
|
|
* Claimer. Each xrebalance self-payment can
|
|
* involve multiple MPP parts arriving at us
|
|
* with the same payment_hash + payment_secret;
|
|
* we want every part to settle, not just the
|
|
* first. For self-rebalancing, partial
|
|
* delivery is still a win -- every msat that
|
|
* settles is real rebalance value. Stale
|
|
* entries are pruned by the periodic timer
|
|
* below on the 24h cadence; in practice every
|
|
* MPP arrives well inside that window. */
|
|
auto id = r.id;
|
|
auto preimage = it->second.preimage;
|
|
|
|
/* Prepare background action. */
|
|
auto act = bus.raise(Msg::ReleaseHtlcAccepted{
|
|
Ln::HtlcAccepted::Response::resolve(
|
|
id, std::move(preimage)
|
|
)
|
|
});
|
|
|
|
/* Launch background action and return. */
|
|
return Boss::concurrent(act).then([]() {
|
|
return Ev::lift(true);
|
|
});
|
|
};
|
|
return bus.raise(Msg::ProvideHtlcAcceptedDeferrer{
|
|
std::move(f)
|
|
});
|
|
});
|
|
|
|
bus.subscribe<Msg::TimerRandomHourly
|
|
>([this](Msg::TimerRandomHourly const&) {
|
|
auto now = Ev::now();
|
|
/* Scan all entries and erase those that have gone
|
|
* past timeout. */
|
|
for ( auto it = entries.begin(), next = entries.begin()
|
|
; it != entries.end()
|
|
; it = next
|
|
) {
|
|
/* Save next entry. */
|
|
next = it;
|
|
++next;
|
|
|
|
if (it->second.timeout < now)
|
|
entries.erase(it);
|
|
}
|
|
|
|
return Ev::lift();
|
|
});
|
|
}
|
|
|
|
std::pair<Ln::Preimage, Ln::Preimage> Claimer::generate() {
|
|
auto pre = Ln::Preimage(rand);
|
|
auto sec = Ln::Preimage(rand);
|
|
|
|
auto h = pre.sha256();
|
|
auto& entry = entries[h];
|
|
entry.timeout = Ev::now() + timeout;
|
|
entry.preimage = pre;
|
|
entry.payment_secret = sec;
|
|
|
|
return std::make_pair(std::move(pre), std::move(sec));
|
|
}
|
|
|
|
}}}
|