diff --git a/Boss/Mod/FundsMover/Attempter.cpp b/Boss/Mod/FundsMover/Attempter.cpp index ddaa025..6d953d4 100644 --- a/Boss/Mod/FundsMover/Attempter.cpp +++ b/Boss/Mod/FundsMover/Attempter.cpp @@ -468,6 +468,38 @@ private: }); } Ev::Io getroute() { + /* FAST-FAIL: if the destination's last-hop fee alone meets + * or exceeds our prorated budget, the maxfee_msat computed + * below clamps to 0 and no route can fit. askrene still + * spends ~1s grinding the flow to the same 205, and the + * Runner then splits and repeats that ~30 times -- the bulk + * of the rebalance storm. Smaller splits only make it worse + * (the budget shrinks toward 0 while the dest base fee does + * not), so every split re-enters here and fast-fails with no + * RPC. Detect the clamp locally and skip the doomed call. + * + * Record the verdict via Msg::FundsMoverUnaffordable, the + * same as the getroutes 205/206 path, so how we detected it + * does not change what the Unaffordable memory sees. The + * three lines below mirror the maxfee_msat computation in the + * getroutes branch; keep them in sync. + */ + { + auto prorata = amount / *remaining_amount; + auto prorated_fee_budget = *fee_budget * prorata; + auto dest_hop_fee = dest_amount - amount; + if (!(prorated_fee_budget > dest_hop_fee)) + return Boss::log( bus, Debug + , "FundsMover[%s]: dest hop fee " + ">= budget; skipping getroutes " + "(no route can fit)." + , attempt_tag().c_str() + ) + + bus.raise(Msg::FundsMoverUnaffordable{ + source, destination, amount, + orig_budget * (amount / orig_amount) + }); + } return Ev::yield().then([this]() { /* Prorate our share of the global fee budget and * pass it to askrene as a hard maxfee_msat cap.