fix(coinmetrics): filter invalid assets in lwba ws transport#5160
fix(coinmetrics): filter invalid assets in lwba ws transport#5160FionnL wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 68cd74c The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
12c9cb4 to
32dfa0c
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the CoinMetrics LWBA WebSocket transport to cache and filter out unsupported base assets so they don’t repeatedly trigger websocket errors/reconnect loops.
Changes:
- Added a shared
invalidBaseAssetscache and filtered invalid bases out during LWBA WS URL generation. - Extended LWBA WS message handling to detect
bad_parametererrors and record the unsupported asset. - Added unit tests for LWBA WS URL generation and message handling, plus a changeset for a patch release.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/sources/coinmetrics/src/transport/lwba.ts | Adds invalid-asset caching/filtering and parses bad_parameter errors to populate the cache. |
| packages/sources/coinmetrics/test/unit/lwba-ws.test.ts | Adds Jest unit tests covering URL generation and message handling for LWBA WS. |
| .changeset/fix-coinmetrics-lwba-invalid-assets.md | Publishes a patch changeset for the adapter update. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const findBaseCurrenciesRegex = new RegExp(/'([^']+)'/g) | ||
| if (message['error']['type'] === 'bad_parameter') { | ||
| //Bad Parameter Error Message | ||
| // type: 'bad_paramter' | ||
| // message: 'Bad parameter 'assets'. Value 'ohmv2' is not supported.' | ||
|
|
||
| const matches = [...message.error.message.matchAll(findBaseCurrenciesRegex)] | ||
|
|
||
| if (matches && !invalidBaseAssets.includes(matches[1][1])) { | ||
| invalidBaseAssets.push(matches[1][1]) | ||
| } | ||
| } |
| it('bad parameter error stores the unsupported asset', () => { | ||
| const res = handleCryptoLwbaMessage(EXAMPLE_BAD_PARAMETER_ERROR_MESSAGE) | ||
| expect(res).toBeUndefined() | ||
| expect(invalidBaseAssets).toContain('ohmv2') | ||
| }) | ||
|
|
||
| it('reorg message results in undefined', () => { | ||
| const res = handleCryptoLwbaMessage(EXAMPLE_REORG_MESSAGE) | ||
| expect(res).toBeUndefined() | ||
| }) |
Good instinct — an infinite reconnect loop from one unsupported asset is a real problem worth fixing. But as written the fix has a crash path and, more importantly, may not actually fix the loop depending on the API's casing. Blocking1. Crash on a malformed const matches = [...message.error.message.matchAll(findBaseCurrenciesRegex)]
if (matches && !invalidBaseAssets.includes(matches[1][1])) {
2. Pushed asset isn't lowercased — the fix may silently no-op — invalidBaseAssets.push(matches[1][1]) // raw case from the API message
// …
desiredSubs.filter(({ base }) => !invalidBaseAssets.includes(base.toLowerCase())) // compares lowercaseThe filter compares against 3. Fragile positional parse — Non-blocking (worth addressing)4. Module-level mutable global with no TTL — 5. No empty- 6. Test gaps (each would have caught a blocker above).
7. Minor. Compile the regex once at module scope, not per message; comment typo ( Not an issueChangeset is fine — one changeset, Verdict: Request changes#1 (crash) and especially #2 (fix silently ineffective on non-lowercase input) are blocking; #3/#4 are the difference between a band-aid and a durable fix. |
Co-authored-by: Cursor <cursoragent@cursor.com>
32dfa0c to
68cd74c
Compare
danwilliams-cll
left a comment
There was a problem hiding this comment.
Automated review — refactor is a net improvement over main (old matches[1][1] code crashed on a malformed single-quote message and mis-stored the param name on the Value 'X' ... for parameter 'assets' word order; new regex is format-independent and lowercases to match the filter). Two low-severity notes inline.
danwilliams-cll
left a comment
There was a problem hiding this comment.
Re-review after the latest push. The three blockers from the previous review have all been addressed by the refactor to a shared getUnsupportedAssetFromBadParameterError helper — good work. Remaining items are non-blocking.
Resolved since last review
Crash on malformed— fixed. Oldbad_parametermessagematches[1][1]threwTypeErroron a message with <2 quoted tokens; helper now returnsundefinedvia.match(...)?.[1], and a "malformed bad parameter error does not throw" test pins it.Pushed asset isn't lowercased— fixed. Helper now.toLowerCase()s the value, matching thebase.toLowerCase()filter; an uppercase-'OHMV2'test covers it.Fragile positional parse— fixed. Regex/\bValue\s+'([^']+)'/ianchors to the value token instead of a positional index, so a reworded message (Value 'x' … for parameter 'assets') still parses correctly.
Non-blocking (worth addressing)
1. Helper assumes every bad_parameter is about an asset — src/transport/error-handling.ts (~L31). A bad_parameter about a different param (e.g. metrics) extracts that token (referenceratexyz) and pushes it into invalidBaseAssets; the base-filter never matches it, so the offending param is never dropped and the reconnect loop persists while the cache accumulates junk. Mitigant: metrics are built as ReferenceRate${quote.toUpperCase()} from VALID_QUOTES, so a bad-metric error is unlikely by construction. Consider gating on the param name ('assets') in the message.
2. No recovery for the all-invalid case — src/transport/price-ws.ts (~L78), same shape in lwba.ts (~L83). If every desired sub's base is cached invalid, the URL is emitted with assets='' (and metrics=''). CoinMetrics rejects empty assets with a bad_parameter that has no Value '...' token → helper returns undefined → nothing cached → reconnect with the same empty URL → loop resumes. Only a logger.warn guards it. Rare (needs all bases invalid), but it's a gap against the PR's stated goal.
3. Module-level mutable global with no TTL — invalidBaseAssets. Process-global, shared across all connections, never cleared. A requested feed is suppressed until process restart even if the rejection was transient or CoinMetrics later re-adds the asset. The added logger.warn on insert covers visibility; a TTL/expiry so it retries rather than suppressing forever would be more robust. (Non-blocking — acceptable for the immediate fix.)
Not an issue
Changeset is fine — one changeset, patch for a fix.
Verdict: Approve (non-blocking nits)
All previously-blocking issues are resolved. Items 1–3 are robustness improvements, not correctness blockers on the common path — safe to merge; addressing them (esp. #1) would harden against edge cases.
Summary
invalidBaseAssetscaching and filtering to the CoinMetrics LWBA WebSocket transport.Test plan
yarn jest packages/sources/coinmetrics/test/unit/lwba-ws.test.ts).Made with Cursor