Skip an unreadable history row instead of valuing or throwing on it - #99
Merged
portdeveloper merged 1 commit intoSep 19, 2026
Merged
Conversation
normalizeHistoryTransaction converted every row with BigInt(tx.value ?? 0). A row the explorer sent without a usable amount took one of two wrong paths: it threw, which aborted the whole /history read including the other endpoint's valid transactions, or it converted to a number the chain never saw. Both paths are now one rejection. parseHistoryAmount considers only a string, a number or a bigint, refuses an empty one, converts inside a try, and refuses a negative result. A row whose amount cannot be read returns null, which is the rejection normalizeHistoryTransaction already uses for a missing hash, so getHistory's existing filter isolates the row without a second mechanism. The quiet half matters as much as the crash: "" , [] and false reached BigInt as 0, true as 1, ["5"] as 5, and an object with a toString as whatever it said. Each printed as a real transfer in /history. Absent now stays absent. Ordering, the result cap and the both-endpoints-failed error are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #98
normalizeHistoryTransactionconverted every row withBigInt(tx.value ?? 0). A row the explorer sends without a usable amount took one of two wrong paths, and only one of them was visible.It threw. The conversion sits inside
getHistory's.map(), so one malformed row aborted the entire read — including the valid transactions the other endpoint had already returned. Reproduced on3e77d03with the productiongetHistoryand an injected fetch: transactions returns one valid row, internal-transactions returnsvalue: "not-a-number", and the call throwsCannot convert not-a-number to a BigIntinstead of returning the one good entry.Or it invented an amount.
BigIntis generous in ways an explorer row is not. Measured on the same commit:tx.value"42","0x2a"," 42 ",42,42n"+5","0b101","0o17","\n42\n"BigInt's own grammar is left alonenull,""," "0n→ printedin +0.0 MONfalse/true0n/1n[]/["5"]0n/5n{ toString: () => "7" }7n"not-a-number","1.5","1e3",{},["5","6"]1.5,NaN,Infinity"-5",-5,-5nin +-0.000000000000000005 MONA fabricated
+0.0 MONline is worse than a missing one: it reads as a real zero-value transfer rather than as a row the client could not parse.What changed
One helper,
parseHistoryAmount. It considers only a string, a number or a bigint, refuses an empty or blank string, converts inside atry, and refuses a negative result.BigInt's own numeric grammar is left alone — hex, binary and octal literals, a leading+, surrounding whitespace all still read. Those are whole non-negative integers written oddly, not rows without an amount, and0x2ais a shape the suite already relied on.A row whose amount cannot be read returns
null, which is the rejectionnormalizeHistoryTransactionalready uses for a missing hash.getHistory's existing.filter(Boolean)isolates it, so no second mechanism appears and the row boundary is where it always was.Ordering, the result cap and the both-endpoints-failed
AggregateErrorare untouched.Tests
381, up from 350. 15 consecutive runs, no failure.
limit, pinning that skipped rows neither disturb newest-first ordering nor consume the cap.Mutation-checked, each guard reverted on its own with a no-op edit as a control:
true,false,[],["5"],toStringtry/catchnullrow rejectionTwo limits worth naming
A skipped row is invisible. Nothing counts or reports what was dropped, so a page of rows that are all unreadable reaches the REPL as "no recent transactions found" — the same ambiguity as the fabricated zero, seen from the other side. That follows from skipping, which is what the issue asks for; if you want a "n rows skipped" line I'd rather do it as its own issue than widen this one.
Precision is lost before this code runs. An unquoted JSON number above 2^53 is already rounded by
JSON.parse:9007199254740993arrives as...992, and the guard faithfully converts the rounded value. Quoted values, which is how explorers in this family sendvalue, are unaffected. Fixing it would mean parsing the response body differently, well outside history normalization.Note on scope
Skipping absent and negative values changes what
/historyprints, beyond repairing the crash. Both were confirmed in the claim thread before I wrote any code: absent values skipped rather than zeroed, negatives treated as malformed. The change stays inside history normalization and its tests.