mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
The third v26.06 deprecation hitting CLBOSS. pay was deprecated in v26.06 with last supported version v27.03 (tighter than getroute's v27.06 window) and is replaced by xpay, which has been available since v24.11 -- so this is a straight migration with no compat bridge required on any CLN version we care about. The sole producer of Msg::PayInvoice is SwapManager paying a Boltz swap-out invoice (Boss/Mod/SwapManager.cpp:541): when CLBOSS does an off-chain to on-chain swap, Boltz issues a bolt11 invoice for the off-chain side and CLBOSS pays it; Boltz then sends the equivalent on-chain. - pay -> xpay - request field bolt11 -> invstring - replace maxfeepercent (a percentage) with maxfee (an absolute msat amount) computed from the decoded invoice amount; the fraction 0.005 preserves the legacy non-MPP 0.5% cap. xpay's own default maxfee is 1% (5000msat floor), which would have doubled our historical fee budget on swap-outs. Because this code path is exclusively swap-out invoice payment -- real money on prod -- we set an explicit absolute maxfee instead of accepting xpay's default. Worst-case extra fee per swap was bounded at 0.5% of the swap amount under the default; this keeps it at the historical 0.5%. - The MPP feature-bit detection (hex-parsing the bolt11 features string and checking bits 16/17) is removed. Its sole purpose was to pick between two maxfeepercent values for pre-xpay pay, whose pre-v0.10 MPP fee-budget management was poor and required a generous 5% cap to succeed. xpay's askrene-driven multi-path routing manages its own per-part fee budget, so we always pass a single absolute maxfee and let xpay split or not as needed. - The Util::Str::HexParseFailure catch path is removed (no longer reachable -- we no longer parse hex features). - The Util/Str.hpp include is dropped. - The nonmpp_maxfeepercent and mpp_maxfeepercent constants are replaced by a single maxfee_fraction. - The decode round-trip and validation that the input is a valid bolt11 invoice. Boltz swap-out invoices are always bolt11, so this stays useful as a sanity check. The decode is now also used to extract amount_msat for the maxfee computation; if the invoice is amountless (which Boltz never emits) the parser throws Jsmn::TypeError and the existing "Unexpected decode result" log path catches it. - retry_for = 1000 (matches historical behavior). - The RpcError / Jsmn::TypeError catching pattern; response parsing is unchanged (we still consume the response as Jsmn::Object _ without reading fields).
132 lines
3.9 KiB
C++
132 lines
3.9 KiB
C++
#include"Boss/Mod/InvoicePayer.hpp"
|
|
#include"Boss/Mod/Rpc.hpp"
|
|
#include"Boss/Msg/Init.hpp"
|
|
#include"Boss/Msg/PayInvoice.hpp"
|
|
#include"Boss/concurrent.hpp"
|
|
#include"Boss/log.hpp"
|
|
#include"Ev/Io.hpp"
|
|
#include"Ev/foreach.hpp"
|
|
#include"Jsmn/Object.hpp"
|
|
#include"Json/Out.hpp"
|
|
#include"Ln/Amount.hpp"
|
|
#include"S/Bus.hpp"
|
|
#include<memory>
|
|
|
|
namespace {
|
|
|
|
/* Maximum routing fee we are willing to pay: 1% of the invoice
|
|
* amount, floored at 5000 msat for small invoices -- the same
|
|
* budget xpay would apply if maxfee were omitted, made explicit
|
|
* here so the policy is visible at the call site and in review.
|
|
* History: the legacy `pay` call used maxfeepercent=5.0 for
|
|
* MPP-capable invoices (all Boltz swap-out invoices are), with
|
|
* realized fees observed under 1%; an earlier xpay-migration pass
|
|
* carried over the 0.5% non-MPP cap instead -- the branch that
|
|
* never applied to these invoices -- which is 2x tighter than
|
|
* xpay's own default and strands swap payments from modestly-
|
|
* connected nodes. A fee-capped failure on the sole PayInvoice
|
|
* producer (SwapManager swap-outs) just stalls inbound-liquidity
|
|
* acquisition, and SwapManager's amount-reduction retries cannot
|
|
* help against a proportional cap. Integer math to avoid float
|
|
* rounding drift on larger invoices.
|
|
*/
|
|
auto constexpr maxfee_divisor = std::uint64_t(100);
|
|
auto constexpr maxfee_floor_msat = std::uint64_t(5000);
|
|
|
|
}
|
|
|
|
namespace Boss { namespace Mod {
|
|
|
|
void InvoicePayer::start() {
|
|
bus.subscribe<Msg::Init
|
|
>([this](Msg::Init const& init) {
|
|
rpc = &init.rpc;
|
|
/* Pay pending invoices. */
|
|
auto f = [this](std::string invoice) {
|
|
return pay(std::move(invoice));
|
|
};
|
|
return Boss::concurrent(
|
|
Ev::foreach(f, std::move(pending_invoices))
|
|
);
|
|
});
|
|
|
|
bus.subscribe<Msg::PayInvoice
|
|
>([this](Msg::PayInvoice const& p) {
|
|
if (!rpc) {
|
|
/* Not yet ready, add to pending. */
|
|
pending_invoices.push_back(p.invoice);
|
|
return Ev::lift();
|
|
}
|
|
return Boss::concurrent(pay(p.invoice));
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> InvoicePayer::pay(std::string n_invoice) {
|
|
auto inv = std::make_shared<std::string>(std::move(n_invoice));
|
|
return Ev::lift().then([this, inv]() {
|
|
return Boss::log( bus, Debug
|
|
, "InvoicePayer: Initiating: %s"
|
|
, inv->c_str()
|
|
);
|
|
}).then([this, inv]() {
|
|
auto parms = Json::Out()
|
|
.start_object()
|
|
.field("string", *inv)
|
|
.end_object()
|
|
;
|
|
return rpc->command("decode", std::move(parms));
|
|
}).then([this, inv](Jsmn::Object res) {
|
|
if (!res.has("type")
|
|
|| std::string(res["type"]) != "bolt11 invoice"
|
|
|| !res.has("valid")
|
|
|| !res["valid"].is_boolean()
|
|
|| !bool(res["valid"])
|
|
|| !res.has("amount_msat")
|
|
) {
|
|
throw Jsmn::TypeError();
|
|
}
|
|
|
|
/* Compute an absolute maxfee from the invoice amount
|
|
* (policy documented on maxfee_divisor above). xpay's
|
|
* MPP handling makes the legacy feature-bit-driven MPP
|
|
* branch unnecessary: askrene + xpay manage the fee
|
|
* budget across parts internally.
|
|
*/
|
|
auto amount = Ln::Amount::object(res["amount_msat"]);
|
|
auto maxfee_msat = amount.to_msat() / maxfee_divisor;
|
|
if (maxfee_msat < maxfee_floor_msat)
|
|
maxfee_msat = maxfee_floor_msat;
|
|
|
|
/* TODO: Get created_at and expiry, add them, then determine
|
|
* current time and subtract, to get retry_for.
|
|
*/
|
|
auto retry_for = 1000;
|
|
|
|
auto parms = Json::Out()
|
|
.start_object()
|
|
.field("invstring", *inv)
|
|
.field("retry_for", retry_for)
|
|
.field("maxfee", maxfee_msat)
|
|
.end_object()
|
|
;
|
|
return rpc->command("xpay", std::move(parms));
|
|
}).then([this, inv](Jsmn::Object _) {
|
|
return Boss::log( bus, Debug
|
|
, "InvoicePayer: Paid: %s"
|
|
, inv->c_str()
|
|
);
|
|
}).catching<RpcError>([this, inv](RpcError const& _) {
|
|
return Boss::log( bus, Debug
|
|
, "InvoicePayer: Failed to pay: %s"
|
|
, inv->c_str()
|
|
);
|
|
}).catching<Jsmn::TypeError>([this, inv](Jsmn::TypeError const& _) {
|
|
return Boss::log( bus, Error
|
|
, "InvoicePayer: "
|
|
"Unexpected decode result for invoice: %s"
|
|
, inv->c_str()
|
|
);
|
|
});
|
|
}
|
|
|
|
}}
|