Headless pairing + signing hosts#264
Draft
pgherveou wants to merge 15 commits into
Draft
Conversation
Add a `truapi-host` binary with three roles that replace the external signing-bot for local end-to-end testing: - `relay`: an in-memory statement-store the two hosts pair over. - `pairing-host`: a seedless host (PairingHostRuntime) that presents a pairing deeplink and bridges product byte-frames over WebSocket. - `signing-host`: a wallet-local host (SigningHostRuntime) that answers the handshake and auto-signs, driven by the new SSO responder. truapi-server gains the §B responder half and the signing-host operations it needs: encode-side handshake helpers and responder codecs in `host_logic/sso/`, v4 extrinsic assembly (`host_logic/transaction.rs`), `runtime/signing_host/sso_responder.rs`, and `sign_payload` / `create_transaction` on the signing host. Product accounts and `rootUserAccountId` derive from `//wallet` (host-spec C.0/C.5) via a new sr25519 hard-junction helper. Bandersnatch ring-VRF product-account aliases (`get_account_alias`) use the `verifiable` crate, and lite-username attestation (`host_logic/attestation.rs` + CLI) registers a People-chain username so `get_user_id` resolves; the pairing host can resolve usernames from the real People chain while SSO stays on the relay (`identity_chain_genesis_hash`). The bun e2e driver (`e2e/`) runs the playground's own generated example sources against the pairing host and writes `explorer/diagnosis-reports/ headless.md`. With `E2E_LIVE_CHAIN=1` and a registered signer account it matches the browser host on every method it passes (43/44; the lone failure is a chain example that sends a deliberately-invalid operation id).
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
The stop_transaction playground example used a hardcoded placeholder operationId; it now broadcasts a transaction and stops it with the returned id, the correct usage. Also emit captured example output for passing methods in the headless diagnosis report and load the e2e signer mnemonic from a gitignored e2e/.env. Note: against the public paseo Asset Hub RPC, transaction_v1_stop rejects broadcast operation ids (-32602), so this method stays red there; it passes on hosts whose transaction API honors stop (e.g. smoldot in the browser host).
…' into headless-host
The headless hosts now connect to the statement store the same way an iOS/web client does: the real paseo-next-v2 People chain, which serves both the statement-store RPC and the chain RPC over one WebSocket. The in-memory relay (relay.rs, the `relay` subcommand, and the relay orchestration in the e2e runner) is removed; both hosts default `--statement-store` to the People node. Statement-store allowance is obtained on-chain. New native module `src/alloc/` ports `Resources.set_statement_store_account` (pallet 63 call 10): metadata-driven signed-extension encoding + inherited-implication proof message (pinned to the live-verified known answer), bandersnatch ring-VRF membership proof via the `verifiable` prover, the unsigned General (v5) extrinsic, `Members` ring fetch, SSS_SLOT slot scan, a minimal WS JSON-RPC client, and `author_submitAndWatchExtrinsic`. Before pairing, the signing host registers allowance for both its own `//wallet//sso` account and the pairing host's device key, proving its LitePeople ring membership. A new `alloc-check` subcommand diagnoses (or `--submit`s) allowance. statement_store_rpc::submit now inspects the SubmitResult: only `new`/ `known` count as accepted, so `NoAllowance`/`BadProof` rejections surface instead of being silently dropped.
…ations
The pairing host is now driven by a product script: `pairing-host --script
foo.ts --product-id p.dot [--auto-accept]` starts the frame server and runs the
script with a global `truapi` injected (the `@parity/truapi` client, scoped to
the product) plus a `host` helper. The Rust CLI spawns the script via an
embedded `js/runner.ts`, and the command exits with the script's status, so the
host command is the test — no separate bun orchestrator.
Both hosts take `--auto-accept`; without it every confirmation a web/iOS host
would show as a modal is prompted y/n on the CLI (ApprovalPolicy::Prompt).
`make headless` builds the CLI + client; `e2e/run.sh` is a thin launcher that
runs a product script through a pairing host and pipes the emitted deeplink to a
signing host. Product scripts live in `js/scripts/`: `battery.ts` (curated
signer gate, 7/7) and `diagnosis.ts` (playground generated examples ->
explorer/diagnosis-reports/headless.md, 43/1/20 with a live chain). The former
`e2e/{run-e2e,driver,diagnosis,ws-provider}.ts` and `wait-and-sign.sh` are gone.
Product ids must be a `.dot`/`localhost` identifier; the default is
`headless-playground.dot`.
Product scripts are now top-level code (no `export default` wrapper); the runner
injects `truapi` and a minimal `host` (`productId` + `productAccount(i)` only,
so accounts stay in sync with `--product-id`). Scripts use `console.log`/`throw`
for everything else. `js/scripts/{battery,diagnosis}.ts` converted accordingly.
The signing host reads its wallet mnemonic from `--mnemonic`, else the
`HOST_CLI_SIGNER_MNEMONIC` env var (or a gitignored `e2e/.env`), else the dev
mnemonic.
Allowance registration now waits for the `set_statement_store_account` extrinsic
to finalize, not just reach a block: the statement store does not honor a freshly
-set allowance at `inBlock`, so the handshake submit raced it and failed with
`NoAllowance`. Verified end-to-end against paseo-next-v2 (7/7 signer battery).
Resolve the headless-host conflicts, add statement-store allowance allocation for SSO, and refresh the headless diagnosis path.
Decode ring revisions correctly for Bulletin allowance claims. Add CLI-managed signer accounts, network presets, and script-capable host modes. Regenerate headless signing and pairing diagnosis reports with zero failures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Get Started
The fast path is two terminals: install the CLI, run the product-side host with a script, then let a wallet-side signing host answer the deeplink it prints.
For the common e2e case,
run.shwraps both processes and pipes the deeplink for you:A product script is top-level JS/TS. The runner injects
truapi, scoped to--product-id, and a smallhosthelper for product accounts:This PR adds the
truapi-hostCLI plus thetruapi-servernative runtime work needed for both host roles. It replaces the external signing-bot dependency for local headless e2e: the two CLI processes pair over the real paseo-next-v2 People-chain statement store and can exercise signing, statement-store, preimage/Bulletin, identity, and chain flows against live RPCs.CLI Shape
One binary has four commands:
pairing-hostsigning-hostidentity-checkalloc-checkThere is no public
--statement-storeflag.--networkselects the identity backend, People RPC, Bulletin RPC, genesis hashes, and account namespace. It defaults topaseo-next-v2.pairing-host--script <path>pairing-host>wherescript <path>can be run repeatedly.--product-id <id>headless-playground.dot.dotorlocalhostid.--frame-listen <addr>127.0.0.1:9955--network <name>paseo-next-v2--base-path <path>$TRUAPI_HOST_BASE_PATH, else XDG state--auto-accepty/nconfirmations.signing-host--script <path>signing-host>.--deeplink <url>deeplink <url>or a rawpolkadotapp://pair?...line.--product-id <id>headless-playground.dot--mnemonic <phrase>$HOST_CLI_SIGNER_MNEMONIC, else none--accountor--lite-username-prefix.--account <name>accounts.json. This is for explicit reuse; auto mode is selected by omitting both--accountand mnemonic.--lite-username-prefix <prefix>headless--base-path <path>$TRUAPI_HOST_BASE_PATH, else XDG stateaccounts.jsonand host state. Account records are network-scoped.--network <name>paseo-next-v2--frame-listen <addr>127.0.0.1:9956--auto-acceptInteractive mode stays alive until
quit/exit/q.pairing-host>supportsscript <path>.signing-host>supportsscript <path>anddeeplink <url>.Auto-Managed Signers
The signing host owns signer readiness instead of relying on one hard-coded mnemonic.
Resolution order:
--mnemonicorHOST_CLI_SIGNER_MNEMONIC: use that mnemonic exactly. No account creation or rotation.--account <name>: load that named account for the selected--network, ensure it is attested and ring-ready, then use it.Fresh auto accounts are generated under
--base-path/<network>/accounts.json, attested through the identity backend, and waited on until People-chain ring membership is visible. The store contains plaintext local test mnemonics and is written with0600permissions on Unix. If a pairing run hits Statement Store slot exhaustion, the account is marked exhausted for the current period and the signing host rotates to another auto-managed account.The CLI deliberately does not use
--account auto: omitting--accountis auto mode. That keeps--account <name>reserved for explicit stored-account reuse.E2E Scripts And Reports
Ready-made scripts live in
rust/crates/truapi-host-cli/js/scripts/:battery.tsrun.sh's default.diagnosis.tsexplorer/diagnosis-reports/ios.md.signing-smoke.tspreimage-smoke.tsDiagnosis now has separate checked-in reports for both host paths:
explorer/diagnosis-reports/headless-signing.md44 passed, 0 failed, 20 skippedexplorer/diagnosis-reports/headless-pairing.md44 passed, 0 failed, 20 skippedThe remaining skipped rows are deferred/non-native feature areas, not the previous diagnosis failures. The formerly failing
Chain/stop_transaction,Preimage/lookup_subscribe,Preimage/submit,Resource Allocation/request,Statement Store/subscribe, legacy signing, andAccount/get_user_idrows are passing in both reports.Resource Allowance
The real statement store enforces per-account allowance. Before answering a pairing deeplink, the signing host grants Statement Store allowance on-chain for both accounts that submit statements: its own
//wallet//ssoaccount and the pairing host's per-pairing device key. It proves LitePeople membership with a bandersnatch ring-VRF and submits the unsigned General v5Resources.set_statement_store_accountextrinsic, then waits for finality before pairing continues.Preimage flows now work because the signing host also allocates product-scoped Bulletin long-term storage allowance over SSO.
Preimage/submitandPreimage/lookup_subscribeno longer depend on a CLI sentinel key with no Bulletin capacity.The low-level allowance code keeps using
subxt-rpcsfor RPC and subscription transport. It still builds dynamic storage keys/extrinsics where no generated static interface exists in this workspace: metadata-driven signed-extension encoding, ring fetch, included-member slicing, slot scans, ring-VRF proof generation, extrinsic assembly, submit-and-watch, and Bulletin authorization polling.Recent review fixes in this area:
alloc-check --submitnow requires--target; the all-zero target remains read-only scan-only behavior.subxt-rpcsinstead of hand-rolled websocket request-id/subscription/storage polling.BulletinRpc::clientpreservesRuntimeFailure; it is stringified only at the SSO response boundary where the protocol type requires text.Runtime Changes
truapi-serveradditions in this PR include:get_user_idresolves for CLI-managed accounts.NoAllowanceandBadProofsurface instead of being treated as accepted submissions.Verification
Ran before the diagnosis-report push:
Live checks against paseo-next-v2:
44 passed, 0 failed, 20 skipped44 passed, 0 failed, 20 skippedLatest review pass:
Other checks:
truapi-host alloc-check --mnemonic <valid mnemonic> --submitexits before connecting with--target is required with --submit; the all-zero default is read-only.make e2e-dotlicompleted with signer-bot on paseo-next-v2 and produced44 success · 0 failed; generated report:playground/test-results/e2e-dotli/diagnosis-report.md.e2e-dotlinow builds the host WASM with the release profile by default because the dev WASM exceeds the dotli PWA size limit. Normaldev-bootstrapstill defaults to the dev profile and can be overridden withTRUAPI_WASM_PROFILE.Reviewer Notes
--networkis the extension point for future chains; adding one should define all RPC/backend/genesis config in one preset.--deeplinkis optional by design. If omitted, the signing host either runs the provided script directly or waits in interactive mode for a pasted deeplink.--mnemonicandHOST_CLI_SIGNER_MNEMONICare still supported for deterministic local runs, but they intentionally disable auto account selection/creation.