XRebalancer: report the closest-to-delivery part as the failure reason
Some checks failed
Code Base Sanity Check / tests (push) Has been cancelled
Code Base Sanity Check / coverage (push) Has been cancelled
Code Base Sanity Check / build-clang (push) Has been cancelled

The transfer-failed/partial reason line took errors[0] -- the failed part
with the lowest partid, which is just MCF's split index and tells you
nothing about how close the cycle came. On a 10-part failure you would see
a random part's from_target.

Surface instead the part that got CLOSEST to delivery: the smallest
from_target magnitude across errors[], with its node and failcode. That
frontier -- how near the best attempt came and which node walled it -- is
the informative number. Parse from_target=-N out of each error string,
pick the minimum N; parts with no parseable from_target (non-204
fallbacks) sort last, and if none parse we keep the first error as before.
The failcode rides along in the surfaced string, so a 0x100c fee-wall vs a
0x1007 liquidity-wall frontier stays distinguishable. When more than one
part failed, the line notes [closest of N] so it is clear the number is
the best of several, not the only one.
This commit is contained in:
Ken Sedgwick 2026-06-06 15:18:13 -07:00
parent 9fdfc130b4
commit 32ba879e6f
No known key found for this signature in database
GPG key ID: DBD2AF0849D711A9

View file

@ -23,6 +23,7 @@
#include"Util/make_unique.hpp"
#include<algorithm>
#include<ctime>
#include<limits>
#include<map>
#include<random>
#include<sstream>
@ -705,16 +706,54 @@ private:
<< " ppm)";
ppm = os.str();
}
/* Chokepoint: the first error reason, if any, folded onto
* the same line. */
/* Chokepoint: among the failed parts, surface the one that got
* CLOSEST to delivery -- the smallest from_target magnitude --
* because that frontier (how near the best attempt came, and
* the node that walled it) is the informative number, not
* whichever part happens to carry the lowest partid. Parts
* with no parseable from_target (non-204 fallbacks) sort last;
* if none parse we keep the first. The failcode rides along in
* the error string, so a 0x100c fee-wall vs 0x1007 liquidity-
* wall frontier stays distinguishable. */
auto reason = std::string();
if (exec.is_object() && exec.has("errors")
&& exec["errors"].is_array() && exec["errors"].size() > 0) {
auto e0 = std::string(exec["errors"][std::size_t(0)]);
for (auto& ch : e0)
auto errs = exec["errors"];
/* "from_target=-N" -> N; sentinel max if absent. */
auto from_target_mag = [](std::string const& s) -> long {
auto key = std::string("from_target=-");
auto pos = s.find(key);
if (pos == std::string::npos)
return std::numeric_limits<long>::max();
pos += key.size();
auto n = 0L;
auto any = false;
while (pos < s.size()
&& s[pos] >= '0' && s[pos] <= '9') {
n = n * 10 + (s[pos] - '0');
++pos;
any = true;
}
return any ? n : std::numeric_limits<long>::max();
};
auto best_i = std::size_t(0);
auto best = std::numeric_limits<long>::max();
for (auto i = std::size_t(0); i < errs.size(); ++i) {
auto m = from_target_mag(
std::string(errs[i]));
if (m < best) {
best = m;
best_i = i;
}
}
auto e = std::string(errs[best_i]);
for (auto& ch : e)
if (ch == '\n' || ch == '\t')
ch = ' ';
reason = "; reason: " + e0;
reason = "; reason: " + e;
if (errs.size() > 1)
reason += " [closest of "
+ std::to_string(errs.size()) + "]";
}
if (delivered > 0.0)
/* Full or partial delivery: settled/total parts and the