Skip to content

Browse does not work on truapi-server #317

Description

@leonardocustodio

Hello guys,

I was using truapi-server and I noticed that browse.dot does not work on it. I asked Fable to analyze it, and this is what it reported. Maybe the best scenario would be to change the Product-SDK to support the truapi-server convention?

TL;DR: browse.dot is broken because of a wire-contract mismatch between the
product's bundled Nova papp SDK and our in-process truapi-server host
: the SDK sends a
self-generated followSubscriptionId ("follow_0") with every follow-scoped chainHead
request, our host only accepts the transport-assigned request id, so every
chainHead_v1_header/storage/call is rejected with "unknown follow subscription id" —
which the product can't even display, because of a second mismatch in the error
encoding that mangles every host error message into an empty string. The
polkadot-api client inside the product then retries at a fixed 250ms forever
(the ChainHead subfollow request failed storm) and the product's own 8s
timeout/reset watchdog loops on top (reviveCall:failedresetBrowseSdk). The
Electron predecessor never hit this because its host is @novasamatech/host-api — the
same vendor as the product SDK — and the Flutter host explicitly tolerates the
self-generated id. The host transport itself (frame server → broker → Asset Hub WS) is
healthy and battery-verified.


follow-id contract mismatch: Nova papp SDK vs truapi-server

This is the root cause of browse.dot not working. Confidence: CONFIRMED (traced in
code at both ends, against the actual installed product bundle).

The product does not open its own WebSocket. Its bundled Nova papp SDK implements a
polkadot-api JsonRpcProvider over our frame protocol:

  • chainHead_v1_follow → host remote_chain_head_follow_subscribe frame subscription.
    The SDK replies to polkadot-api with a synthetic subscription id — follow_${n}
    from a local counter (PapiProvider, product bundle main-pretty.js:15534-15920).
  • Every subsequent chainHead_v1_header/storage/call passes that synthetic id verbatim
    as followSubscriptionId.
  • The truapi wire contract requires the transport-assigned request id of the subscribe
    start frame
    (truapi/src/transport.ts:22-28; the upstream playground does
    followSubscriptionId: sub.subscriptionId). The Nova SDK's transport doesn't even
    expose that id (its subscribe() returns only {unsubscribe, onInterrupt},
    main-pretty.js:5626-5677) — the product structurally cannot comply.
  • truapi-server registers follows under follow_id(cx.request_id())
    (runtime.rs:1529) and looks the product-supplied string up
    (runtime.rs:1545/1577/1593) → require_remote_follow misses →
    RuntimeFailure::host_failure("unknown follow subscription id …")
    (chain_runtime.rs:997-1028) → JSON-RPC error -32603 back to the product.

The follow subscription itself works (the Initialized event arrives via the
subscription stream, which needs no follow id) — only request-shaped follow-ups die.
Those follow-ups are exactly polkadot-api observable-client's "enhanced follow"
subfollow requests fired on Initialized: getRawHeader(block) plus
storage(block, "hash", "0x3a636f6465") (the :code hash, for runtime-change
detection) — matching the console message verbatim. With the follow never settling,
the papi client never obtains a runtime context, so ReviveApi dry-run calls
(reviveCall, the dotNS resolution path) are never dispatched and hit the product's
own 8s timeout.

Why it works on polkadot-desktop / mobile:

  • The Electron host is @novasamatech/host-api / host-substrate-chain-connection
    0.8.10 (polkadot-desktop/package.json:104-110) — the same vendor as the product's
    papp SDK
    ; its host tolerates its own SDK's conventions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions