paritytech / paritytech/triangle-js-sdks
Several bugs in host-container and host-api surfaced while porting
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3
- Forks
- 1
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 8
Description
While porting pieces of triangle-js-sdks into a downstream codebase we uncovered a number of bugs in the host-container / host-api sources. The same patterns still appear on main at the time of writing. Filing here for tracking — happy to open follow-up PRs if useful.
1. handleSubscription leaks the handler's cleanup when interrupt() is called asynchronously
File: packages/host-api/src/transport.ts (around L351–367, the handleSubscription start handler).
The interrupt callback passed to the handler does interrupted = true; subscriptions.delete(requestId) and posts an interrupt message, but the handler's returned cleanup function is only invoked on the synchronous-interrupt branch (if (interrupted) { unsubscribe(); }). If the handler stashes interrupt and calls it asynchronously (e.g. from a setInterval tick), the stored unsubscribe is removed from the map without ever being called — intervals, listeners, sockets etc. leak.
2. chainHead_v1_follow rejection is silently swallowed
File: packages/host-container/src/chainConnectionManager.ts (around L534–537, startFollow's pending-request reject).
The reject handler only clears pendingRequestId and removes the follow from followSubscriptions. Nothing propagates the failure back to the subscriber. If the node rejects the RPC (method unsupported, connection failure, etc.) the product-side subscription stays "alive" from the transport's perspective but never receives events and never sees an interrupt — it just hangs forever.
3. followSubscriptionId on chain-op requests is ignored; host arbitrarily picks one follow per chain
File: packages/host-container/src/createContainer.ts (L708–716 for remote_chain_head_header and the same pattern across _body, _storage, _call, _unpin, _continue, _stop_operation).
Each handler discards followSubscriptionId from the incoming request and calls manager.getChainFollowSubId(genesisHash), which returns the first follow found on that chain (chainConnectionManager.ts L584–587). With multiple concurrent follows on the same chain (e.g. one with runtime, one without — both legal), every chain-op routes to an arbitrary one and silently uses the wrong pin set.
4. JSON-RPC error code/message are discarded on chain-op failures
File: packages/host-container/src/createContainer.ts (every chain-op catch — e.g. L719, L741, L773, L800, L822, L844, L866).
Each catch does new GenericError({ reason: String(e) }), while sendRequest in chainConnectionManager.ts (L473) rejects with the raw JSON-RPC parsed.error object ({code, message, data?}). String({...}) is '[object Object]' — so the node's actual error code and message never reach the product, which then has to hardcode -32603 when forwarding to the JSON-RPC caller.
Happy to provide more detail or split into separate issues — let me know which is more useful for tracking.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the cited handlers in packages/host-api/src/transport.ts, packages/host-container/src/chainConnectionManager.ts, and packages/host-container/src/createContainer.ts. Trace interruption, follow rejection, followSubscriptionId routing, and sendRequest error handling. Done means asynchronous cleanup runs, follow failures reach subscribers, chain operations use the requested follow, and JSON-RPC code and message are preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100