Summary
On the dotli web host (paseo.li, release 0.9.2 on 2026-10-02, @parity/truapi-host 0.23.0), a
product's createTransaction failed with:
createTransaction failed: Unknown: cannot select a metadata block: Cannot construct OnlineClientAtBlock:
cannot get the current block: Backend error: RPC error: RPC error: subscription dropped.
The same signer's next attempt, 5 minutes later, built, signed and included the transaction.
Three things combine:
- A single refused re-follow ends the core's Subxt chainHead follow for good, and the
transaction being built fails with it.
- The core rebuilds the connection only on the next call, so the failing call itself is never
retried.
- The error reaches the product as
Unknown. The core classifies it as chain-unavailable,
but nothing on the wire tells the product it is retryable.
Seen in Coffer, a multisig treasury product, on coff3r.paseo.li. The product account's first
approve_as_multi failed this way about 4 s after Coffer shared the proposal off-chain. Coffer then
withdrew the proposal, and the user had to propose again.
Where (at @parity/truapi-host@0.23.0, 37e2d9f)
rust/crates/truapi/src/host_internal/extrinsic.rs:332. A failure to build
OnlineClientAtBlock becomes LocalTransactionError::ChainUnavailable("cannot select a metadata block: …"). For a product account the web host builds the transaction locally
(pairing_host.rs).
rust/crates/truapi/src/runtime.rs:1083-1085. AuthorityError::Unavailable and
AuthorityError::Unknown both map to v01::HostCreateTransactionError::Unknown { reason }.
rust/crates/truapi/src/chain_runtime.rs:1018-1036.
- The Subxt driver's errors are logged only at
debug (chainHead backend error=…), so the
cause never reaches a web console.
- When the driver ends, the cached Subxt bundle is invalidated so that the next caller rebuilds
it. That is why the later attempt succeeded.
- Subxt 0.50.3.
RpcError::SubscriptionDropped is produced by Subxt itself, in
ChainHeadBackend::latest_finalized_block_ref, when the follow stream ends before an
initialized event.
- The follow stream stops for good on any re-follow or stream error other than "disconnected,
will reconnect" (follow_stream.rs).
The dotli side
dotli's core chain connection (packages/ui/src/host-callbacks/Chain.ts) answers a follow during a
chain or frame halt with a "chain halted" error while its redial gate is shut. The gate stays shut
1 s after a frame halt and backs off to 30 s (redial-gate.ts). The gate is written for a client
that re-follows by itself (redial-gate.ts:6: "papi client re-follows every 250 ms"). Subxt does
not, so one refused re-follow is final.
Not determined:
- whether this case was a chain halt, a frame halt, or a light-client
stop followed by a refused
re-follow;
- which backend the user ran: smoldot, the default, or the RPC gateway.
dotli logs halts only in debug builds.
Proposed changes
- Retry inside
createTransaction. When building the client fails because the follow
ended (SubscriptionDropped, or a backend whose driver has exited), rebuild the Subxt bundle
and retry within the call, with a short backoff that outlasts dotli's 1 s redial gate. Nothing
has been signed or broadcast at that point, so a retry is safe.
- Report chain-unavailable as its own wire error. Add a
HostCreateTransactionError variant
(or equivalent) for chain-unavailable instead of folding it into Unknown. A product can then
retry on that variant alone. Today the only way is matching the reason string.
- Log the backend error above
debug. Then the console shows why the follow ended.
Summary
On the dotli web host (paseo.li, release 0.9.2 on 2026-10-02,
@parity/truapi-host0.23.0), aproduct's
createTransactionfailed with:The same signer's next attempt, 5 minutes later, built, signed and included the transaction.
Three things combine:
transaction being built fails with it.
retried.
Unknown. The core classifies it as chain-unavailable,but nothing on the wire tells the product it is retryable.
Seen in Coffer, a multisig treasury product, on coff3r.paseo.li. The product account's first
approve_as_multifailed this way about 4 s after Coffer shared the proposal off-chain. Coffer thenwithdrew the proposal, and the user had to propose again.
Where (at
@parity/truapi-host@0.23.0, 37e2d9f)rust/crates/truapi/src/host_internal/extrinsic.rs:332. A failure to buildOnlineClientAtBlockbecomesLocalTransactionError::ChainUnavailable("cannot select a metadata block: …"). For a product account the web host builds the transaction locally(
pairing_host.rs).rust/crates/truapi/src/runtime.rs:1083-1085.AuthorityError::UnavailableandAuthorityError::Unknownboth map tov01::HostCreateTransactionError::Unknown { reason }.rust/crates/truapi/src/chain_runtime.rs:1018-1036.debug(chainHead backend error=…), so thecause never reaches a web console.
it. That is why the later attempt succeeded.
RpcError::SubscriptionDroppedis produced by Subxt itself, inChainHeadBackend::latest_finalized_block_ref, when the follow stream ends before aninitializedevent.will reconnect" (
follow_stream.rs).The dotli side
dotli's core chain connection (
packages/ui/src/host-callbacks/Chain.ts) answers a follow during achain or frame halt with a "chain halted" error while its redial gate is shut. The gate stays shut
1 s after a frame halt and backs off to 30 s (
redial-gate.ts). The gate is written for a clientthat re-follows by itself (
redial-gate.ts:6: "papi client re-follows every 250 ms"). Subxt doesnot, so one refused re-follow is final.
Not determined:
stopfollowed by a refusedre-follow;
dotli logs halts only in debug builds.
Proposed changes
createTransaction. When building the client fails because the followended (
SubscriptionDropped, or a backend whose driver has exited), rebuild the Subxt bundleand retry within the call, with a short backoff that outlasts dotli's 1 s redial gate. Nothing
has been signed or broadcast at that point, so a retry is safe.
HostCreateTransactionErrorvariant(or equivalent) for chain-unavailable instead of folding it into
Unknown. A product can thenretry on that variant alone. Today the only way is matching the reason string.
debug. Then the console shows why the follow ended.