v0.2.20 — HTTP 418 is an IP ban, not a heavy 429 - #9
Open
WickHunter wants to merge 1 commit into
Open
Conversation
`httpRefusal` folded 418 and 429 into one `RateLimitError`, so a 418 bought the rate-limit treatment: halve the rate, go quiet for 60s (doubling, capped at 15 minutes), resume. On a Binance-family venue — which is what Aster is — 429 means "you are going too fast" and 418 means THIS IP IS ALREADY BANNED, for 2 minutes to 3 days by the venue's own manual, which is explicit that further requests are what lengthen it. The hub therefore resumed inside the ban, on a one-minute tick, extending it, for as long as the service ran — and the panel called that COOLING, which the README tells the operator is "the collector working, not a fault". An IP ban looked like health, on the one venue where a ban takes history away from every install at once. The bot repo settled this the other way and wrote down why (liqhunter src/venues/rate-limited.ts, v0.86.80). The hub and a bot share one IP whenever they share a box; they may not disagree about what a ban is. - `VenueBanError extends RateLimitError`, so every existing caller keeps its "stop the pass, do not count as FAILING" handling. That inheritance is also the trap: the ban flag is the NARROWER question, so the collector must ask it first. Asserted through the collector, mutation-verified by swapping branches. - Its own wait: 15m doubling to the documented 3-day maximum, not the rate limiter's 60s-to-15m ladder. `Retry-After` and a `banned until <epoch>` body are used only to LENGTHEN it, never to shorten. - Its own silence: nothing sent at all, not even the instrument list. - Its own state (BANNED, red, outranks COOLING/STALLED/FAILING) and its own lifetime counter, which stays on the card after the ban lifts. - Rate to the floor, ceiling halved, and the escalation does not reset on a success — cleared by a restart, which is an operator deciding they dealt with the cause. - 418 is a ban on all four venues: the other three do not document sending it, so it is a status we do not understand, and the safe reading is silence. - Own env knobs (HUB_CANDLE_BAN_COOLDOWN_MS / _MAX_), floored at the venue's documented minimum ban. tests/venue-ban.test.mjs — 24 checks; all eight mutations tried caught by name. npx tsc clean, all 14 suites green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AWoRUxBefVAq5b23sZmZdC
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.
What this is, and what it is not
The task was "get hub and Aster ready". The hub already collects and serves Aster — that shipped in v0.2.19 (PR #8) and it is good work. This PR does not rebuild it. It fixes the one thing in that path that would have turned a routine rate limit into a multi-day outage, and it is the only hard requirement of the brief that the shipped code did not meet.
The defect
httpRefusalfolded HTTP 418 and 429 into oneRateLimitError, so a 418 got the rate-limit treatment: halve the rate, go quiet for 60 s (doubling, capped at fifteen minutes), resume.On a Binance-family venue — which is what Aster is — those two statuses do not mean the same thing:
429418Aster's own manual gives the ban as 2 minutes to 3 days, and is explicit that continuing to send requests is what lengthens it. So the hub resumed inside the ban, on a one-minute tick, extending it, for as long as the operator left the service running.
And the exchanges panel called that COOLING — a state the README tells the operator in as many words is "the collector working, not a fault". An IP ban looked like health, on the one venue where a ban takes history away from every install at once rather than from one.
The bot repo had already settled this exact question the other way and written down why (
liqhunter src/venues/rate-limited.ts, v0.86.80):The hub and a bot share one IP whenever they share a box. They may not disagree about what a ban is.
The fix
VenueBanError extends RateLimitError— the inheritance is load-bearing in both directions. Every existing caller asksisRateLimitand keeps its "stop the pass, do not count this as FAILING" handling, which was always right for a ban too, so nothing had to be found and updated. It is also the trap: the ban flag is the NARROWER question, so a collector that asks the wider one first classifies every ban as a slow-down and the fix does nothing. That ordering is asserted through the collector, not read off the source, and mutation-verified by swapping the two branches.A 418 then gets its own everything:
Retry-After, and abanned until <epoch>deadline in the venue's own error body, are used only to LENGTHEN it, never to shorten. That body parse is not verified against a live Aster ban — this box has never been banned by this venue and will not arrange to be — so it is written to be worthless rather than wrong: an unparseable body leaves the collector on its own schedule, and a hallucinated timestamp cannot talk the hub into probing an active ban.BANNED, red, outranking COOLING / STALLED / FAILING, because all three of those send an operator looking for the wrong thing. The detail names the ban, the time left, the lowered ceiling, that requests lengthen it, and that this is not the collector working.418 is a ban on all four venues, not just Aster. The other three do not document sending it, so a 418 from one of them is a status this hub does not understand — and the safe reading of a status we do not understand is the one that makes the hub go quiet.
The ban schedule gets its own two env knobs rather than borrowing the rate-limit pair (shortening a rate-limit backoff is reasonable and must not silently shorten how long an IP ban is waited out), and both are floored at the venue's documented minimum ban — a wait under that ends inside every ban there is.
Proof
npx tscclean, all 14 suites green. Newtests/venue-ban.test.mjs, 24 checks. Every mutation tried was caught, by name:RateLimitError(the defect, restored)418 is a BAN on every venue here…isRateLimitbeforeisVenueBanTHE ORDERING: a ban reaches BANNED, not COOLINGBANNEDno longer outranksFAILINGTHE ORDERING: a ban reaches BANNED, not COOLINGTHE WAIT is the ban ladder…THE WAIT is the ban ladder…opts.requestsPerSecondthe creep-back respects the lowered ceiling…a banned collector says NOTHING to the venuethe venue's deadline is taken when it is LONGER…Restores were done by re-applying the inverse edit against recorded
sha256hashes, withnpx tscbefore each gate — nevergit checkout.Unchanged on purpose
v1, the canonical key order,keyId: seed-1signed by the licence key. No bot-side change is needed for Aster — the bot's seeder is handedvenue: olbVenuedirectly and the envelope'svenueis a plain string, Aster islive+klinesPageEndMssoolbVenueCandleSupportpasses it, andklinesIncludeForming: truemeansolbVenueKlineFetcheralready strips the forming bar before the exact cross-check. The pendingcandle-1key rollout is unrelated to Aster and still gated behind the README's four-step order.x-mbx-used-weight-1mreadout backing off at 80%.dropUnclosed, thennotAfterMsinsideCandleStore.write), and there is no interval lookup table anywhere in the hub to have a?? "1m"fallback in — each adapter hard-codes its own 1-minute spelling.Generated by Claude Code