Repository navigation
feat(cli): connect through truapi-provider, with a light-client backend - #1664
Conversation
The CLI's chain provider keeps its routing table (each served role to its own endpoint, the People URL as the fallback for the SSO sentinel and any unmapped genesis) and dials the chosen URL through truapi_provider::connect_rpc_node, a new export of the provider's existing WebSocket backend. The CLI's own WsJsonRpcConnection is removed, along with its tokio-stream dependency and tokio-tungstenite's client TLS features. A URL that fails to parse is a connect error, as before. Transport behaviour the CLI now inherits from the provider: - a ping goes out after 20s without an outgoing request, and 50s with no incoming frame ends the response stream, so a half-open socket is replaced instead of hanging - close() and drop close the socket, and a full or failed send ends the stream - one pull-based response stream per connection, so no response is dropped under load Provider changes: - the native WebSocket backend checks wss certificates against the bundled webpki roots, sharing one client config with the light client's wss tunnel, instead of the platform verifier. Native dials behave the same on every target, and the static musl CLI binary keeps working without a system certificate store - messages up to 64 MiB pass in both directions. jsonrpsee's 10 MiB default would reject a Bulletin store transaction carrying a preimage over about 5 MiB The routing tests are unchanged. A loopback test checks that a frame sent before responses() is read arrives, that a closed socket ends the stream, and that a later connect replaces it. A provider loopback test checks that a 12 MiB message round-trips. Closes #1511
|
CI Status: 24 required jobs green, 22 passed and 2 skipped by path filter. All job results
Signing credentials: valid as of 2026-11-03 Commit |
Bundle size reportCompared with
WebAssembly modules
No file changed size. Commit: 23d35b3 |
…rovider # Conflicts: # rust/crates/truapi-host-cli/src/chain.rs
|
sharing some early notes from agents. some things might be worth doing in a follow up. 1. Use the provider abstraction and keep its implementation privateRemove the new Configure and share an The new TLS configuration helper also introduces an initialization dependency on the caller: with both Rustls crypto providers enabled, it can panic before connecting unless the application has installed a process-wide default. This was reproduced locally. Make initialization self-contained or leave that TLS change out of this PR. 2. Route comparable host functionality through the same providerInclude the main host chain path, dotNS/identity reads and Bulletin preimage retrieval. Dotli is the reference for shared access: its name resolver receives a shared provider and its Bitswap client uses the remote Bulletin provider. Dotli's gateway WebSockets still use a separate transport, so matching dotli does not mean copying that implementation or claiming it already routes everything through 3. Let the CLI user select the backendAdd an option such as Every consumer migrated in this PR must honor the selection. Preserve the intended endpoint overrides, People-chain fallback and local-network validation. Coordinate with the existing light-client proposal, PR #1052. Verify both selections, including dotNS and Bulletin access, and reconnection through the selected provider. Update the CLI documentation to name any remaining exceptions instead of saying that every chain connection uses the provider. Add the follow-up work below to the PR description or a linked tracking item. Follow-upEnforce one backend policy throughout
|
| Path | Remaining direct connection |
|---|---|
| Signer setup and ring-membership polling | accounts.rs:708 |
| Full-name registration | People and Asset Hub |
| Endpoint genesis validation | network.rs:239 |
| PGAS and statement-allowance diagnostics | People, Asset Hub and allowance |
These use an allowance helper that opens Subxt transport directly. Define how standalone commands and candidate-endpoint validation use the provider without requiring a full host runtime. Document the exceptions until they are migrated.
Clarify the provider's native certificate policy
The new certificate configuration switches every native WebSocket consumer from OS verification to bundled roots, so OS-installed CAs stop being trusted. Decide and document that policy for the provider as a whole. Keep the CLI integration from depending on this broader change unless it is explicitly intended.
…ugh the provider Addresses the review on #1664. The CLI's chain provider (WsChainProvider, now CliChainProvider) builds an EmbeddedChainProvider instead of dialing through a public truapi_provider::connect_rpc_node, which is removed: backend dialing stays private to the provider crate. AssetHubReader and BitswapRpc connect through the same provider, wrapping the connection in truapi::HostRpcClient (now exported) to get a subxt client. --chain-backend rpc|light-client (TRUAPI_CHAIN_BACKEND) selects the backend, rpc by default. The light client registers the network from the provider's bundled catalog; one per network serves the process and keeps one idle connection per chain so smoldot does not stop a chain between uses. Routing is the same on both backends, and only routed hashes reach the provider, so an RPC run never falls into the catalog lookup and starts smoldot. The light client is refused for polkadot, with the HOST_CLI_*_WS overrides and with a local network; the never-wired TRUAPI_LIGHT_CLIENT check is replaced by it. The native TLS config names the ring provider instead of relying on a process-wide default, which rustls refuses to pick when a build enables two. The bundled-roots certificate policy is documented for the crate as a whole. The remaining direct RPC paths (ring-membership poll, register-name's transactions, rerouted-endpoint genesis check, alloc-check, pgas-check) are named in the README and SPEC.
TarikGul
left a comment
There was a problem hiding this comment.
Approving to not block, but looks solid
Drop the 60 s request timeout around CliChainProvider::rpc_client. HostRpcClient already closes a connection that delivers no frame for 30 s while a request waits, and keepalive pongs never reach it, so the wrapper only fired later than that. Dropping the request future on timeout also left its id pending in HostRpcClient, which could close the connection on a later idle stretch and never unsubscribed a late subscription. The SPEC and changeset now state the 30 s silence limit. Drop the process-wide rustls provider install and the CLI's direct rustls dependency. truapi-provider names ring in its TLS config, reqwest falls back to ring, and the CLI's tree compiles no other provider. The workspace rustls entry had no other user. Refuse `pgas-check --submit` on the light client before deriving keys or dialing, since smoldot drops the claim and the command would only wait out the submit timeout. Name where a chain is served in every connect error: the RPC URL it routes to, or the light client. CliChainProvider::location replaces the test-only routed_url.
smoldot sends nothing on a connection while it syncs, so a CLI command whose first request went out during a sync longer than HostRpcClient's 30 s silence deadline failed. CliChainProvider::rpc_client now waits on the light client's lifecycle until the chain is Ready before handing out a client. The wait fails on a stall of the chain or of the relay it syncs through. smoldot reports a parachain's stall from its own peers only, so People, Asset Hub and Bulletin would otherwise wait forever behind a relay whose warp sync stopped. The core's own connections are not affected: they go through connect, and chainHead_v1_follow answers its subscribe at once.
until_ready took the chain's and the relay's lifecycle merged with stream::select, which ends only once both inputs end. A chain the light client stopped running would then have been waited on for as long as its relay kept reporting. It now takes the two streams separately: the end of the chain's stream ends the wait, and the end of the relay's only drops that branch. Telling the two apart no longer compares genesis hashes.
Closes #1511. Supersedes #1052. Follow-ups: #1715.
Every chain connection the CLI makes goes through
CliChainProvider(src/chain.rs), built ontruapi-provider'sEmbeddedChainProvider. That includes the standalone commands and the genesis check of a rerouted endpoint, which gets a provider for that endpoint alone. Standalone commands wrap provider connections intruapi::HostRpcClientand give each request 60 s.--chain-backend rpc|light-client(orTRUAPI_CHAIN_BACKEND) selects the backend,rpcby default. The light client coverspaseo-next-v2andpreviewnet, and is refused with theHOST_CLI_*_WSoverrides or a local network. One light client per network serves the process and holds one idle connection per chain, so smoldot keeps the chain running. Only routed hashes reach the provider, so RPC never starts smoldot. smoldot drops PGAS claims on Asset Hub (SPEC §14.3, #1715). The light client adds about 3.3 MiB to the binary.In
truapi-provider:ChainProvider::connect; the crate exports no connect helper.wssverifies against the bundled webpki roots, and the TLS config namesringitself, so no process-wide crypto default is needed. Only the CLI uses the nativewsbackend; the mobile provider packages build without it.Verification
Unit tests cover routing, reconnects, the People fallback, the light client keeping a chain running, request timeouts, the light-client refusals, the TLS default and a 12 MiB round trip.
cargo test -p truapi-host-cli --bin truapi-host reaches_paseo -- --ignoredchecks both backends against Paseo Next v2.On Paseo Next v2, with a fresh identity per backend:
identity-checkalloc-check/pgas-checkscansattest(ring-membership poll)alloc-check --submitpgas-check --submitregister-nameto the full-person checkregister-nameitself was not submitted: it needs a full person, and the only one available is the shared dev account, which can register once.