Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/allow-wasm-bulletin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@parity/truapi-host": patch
---

Allow WASM signing hosts to allocate Bulletin allowances through the canonical runtime services.
4 changes: 2 additions & 2 deletions rust/crates/truapi-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ x25519-dalek = { version = "2", default-features = false, features = ["static_se
sha2 = "0.10"
merlin = "3"
parking_lot = "0.12"
frame-metadata = { version = "23", default-features = false, features = ["std", "current", "decode"] }
scale-info = { version = "2.11", default-features = false, features = ["decode"] }

tracing = "0.1"
# `registry` + `std` only: pulls the Registry + per-layer filter/reload, but
Expand All @@ -76,8 +78,6 @@ tokio-tungstenite = { version = "0.21", default-features = false, features = ["h
uniffi.workspace = true
subxt = { version = "0.50.3", default-features = false, features = ["native"] }
subxt-rpcs = { version = "0.50.3", default-features = false, features = ["jsonrpsee", "native"] }
frame-metadata = { version = "23", default-features = false, features = ["std", "current", "decode"] }
scale-info = { version = "2.11", default-features = false, features = ["decode"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
futures-timer = { version = "3", features = ["wasm-bindgen"] }
Expand Down
8 changes: 4 additions & 4 deletions rust/crates/truapi-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,10 @@ role-specific lifecycle, so no method exists on a role that can't mean it:
exponent, and revision reads to one finalized block before creating a proof.
Extrinsic-payload signing and v4 transaction construction work from
pre-encoded payload fields, so no chain metadata is needed;
statement-store and Bulletin allowance allocation are native-only (wasm
builds report them as unavailable) and do need metadata, which they take from
the `RuntimeServices`-owned per-chain cache rather than re-reading it per
call.
Bulletin allowance allocation works on native and wasm hosts, while
statement-store allowance allocation remains native-only. Both need runtime
metadata, which they take from the `RuntimeServices`-owned per-chain cache
rather than re-reading it per call.

`host_logic` stays pure: the orchestrators above call into it for codecs,
session/SSO crypto, key derivation, and permission policy, while all I/O
Expand Down
1 change: 0 additions & 1 deletion rust/crates/truapi-server/src/chain_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,6 @@ impl ChainRuntime {
}

/// Raw JSON-RPC client for the chain identified by `genesis_hash`.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn rpc_client(
&self,
method: &'static str,
Expand Down
1 change: 0 additions & 1 deletion rust/crates/truapi-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ pub use runtime::ResponderExit;
#[cfg(not(target_arch = "wasm32"))]
pub use runtime::StatementRenewalTarget;
pub use runtime::login_failure::reports_exhausted_period;
#[cfg(not(target_arch = "wasm32"))]
pub use runtime::statement_allowance;
pub use truapi_platform::{
CoreStorageKeyDescription, CoreStorageKeyDescriptionError, HostRuntimeConfig,
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/truapi-server/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ mod signing_host;
pub(crate) mod sso_pairing;
/// SSO remote request/response messaging over the statement store.
pub(crate) mod sso_remote;
/// Native Statement Store and Bulletin allowance allocation.
#[cfg(not(target_arch = "wasm32"))]
/// Statement Store and Bulletin allowance allocation. The Bulletin claim
/// path compiles for every target; PGAS and renewal stay native-only.
pub mod statement_allowance;
/// `StatementStore` surface: proofs plus submit and subscribe flows.
pub(crate) mod statement_store;
Expand Down
1 change: 0 additions & 1 deletion rust/crates/truapi-server/src/runtime/bulletin_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ impl BulletinRpc {
}

/// Open a raw RPC client over the configured Bulletin chain.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn client(
&self,
label: &'static str,
Expand Down
6 changes: 2 additions & 4 deletions rust/crates/truapi-server/src/runtime/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ pub(crate) struct RuntimeServices {
pub(crate) statement_store: StatementStoreRpc,
/// In-core Bulletin submission over the configured Bulletin chain.
pub(crate) bulletin: BulletinRpc,
/// Runtime metadata and chain state shared by the native allowance
/// paths, per chain.
#[cfg(not(target_arch = "wasm32"))]
/// Runtime metadata and chain state shared by the allowance paths, per
/// chain.
pub(crate) chain_context: crate::runtime::statement_allowance::ChainContextCache,
/// Values from confirmed in-core submissions, served to `lookup_subscribe`
/// until the host's content backend has them. Byte-bounded, oldest-first.
Expand Down Expand Up @@ -79,7 +78,6 @@ impl RuntimeServices {
chain,
statement_store,
bulletin,
#[cfg(not(target_arch = "wasm32"))]
chain_context: crate::runtime::statement_allowance::ChainContextCache::default(),
preimage_cache: Mutex::new(PreimageCache::default()),
statement_cache: Mutex::new(StatementCache::default()),
Expand Down
9 changes: 3 additions & 6 deletions rust/crates/truapi-server/src/runtime/signing_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
//! Implemented: local session lifecycle, raw-bytes signing, extrinsic-payload
//! signing, v4 transaction construction (payload fields and extensions arrive
//! pre-encoded, so no chain metadata is needed), RFC-0007 product entropy,
//! bandersnatch ring-VRF aliases and membership proofs, and product-scoped
//! Statement Store and Bulletin allowance keys (native only).
//! bandersnatch ring-VRF aliases and membership proofs, native Statement Store
//! allowance keys, and cross-target Bulletin allowance keys.

#[cfg(not(target_arch = "wasm32"))]
mod allowance_renewal;
Expand Down Expand Up @@ -44,22 +44,20 @@ use crate::host_logic::extrinsic::{
Sr25519Signer, V5BuildError, build_signed_extrinsic_v4,
build_signed_extrinsic_v4_with_signature, build_signed_extrinsic_v5,
};

use crate::host_logic::product_account::{
ProductAccountError, SR25519_SIGNING_CONTEXT, derivation_index_bytes, derive_identity_keypair,
derive_product_keypair, derive_product_subtree_keypair, derive_ring_vrf_entropy,
derive_root_keypair_from_entropy,
};
#[cfg(not(target_arch = "wasm32"))]
use crate::host_logic::product_account::{
derive_full_person_ring_vrf_entropy, derive_lite_person_ring_vrf_entropy,
};
use crate::host_logic::session::{SessionInfo, SessionState};
use crate::host_logic::sso::messages::{OnExistingAllowancePolicy, RingVrfError};
use crate::host_logic::transaction::{extrinsic_payload_extensions, extrinsic_payload_preimage};
use crate::runtime::auth_state::AuthStateMachine;
#[cfg(not(target_arch = "wasm32"))]
use crate::runtime::statement_allowance::CollectionCandidate;
#[cfg(not(target_arch = "wasm32"))]
use crate::runtime::statement_allowance::collection::PersonhoodCollection;
use ring_vrf::{
ChainRingResolver, MemberCandidate, RingResolver, alias_from_entropy, context_bytes,
Expand Down Expand Up @@ -388,7 +386,6 @@ impl SigningHost {
/// actually a member of is settled on chain by looking for a ring that
/// includes each member key, not by local state. That keeps the two hosts
/// from disagreeing about personhood.
#[cfg(not(target_arch = "wasm32"))]
fn reserved_person_collection_candidates(
&self,
session: &AuthoritySession,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ use truapi_platform::{
};

use super::SigningHost;
#[cfg(not(target_arch = "wasm32"))]
use crate::chain_runtime::RuntimeFailure;
use crate::host_logic::entropy::root_entropy_source;
#[cfg(not(target_arch = "wasm32"))]
use crate::host_logic::product_account::derive_sr25519_hard_path;
use crate::host_logic::product_account::{
ProductAccountError, derive_identity_keypair, derive_ring_vrf_domain_entropy,
Expand Down Expand Up @@ -56,18 +54,15 @@ use crate::runtime::authority::{
};
use crate::runtime::services::RuntimeServices;
use crate::runtime::sso_remote::fresh_statement_expiry;
#[cfg(not(target_arch = "wasm32"))]
use crate::runtime::statement_allowance::StatementAllowanceError;
use crate::runtime::statement_store_rpc;
#[cfg(not(target_arch = "wasm32"))]
use crate::runtime::statement_store_rpc::StatementStoreRpcClientError;

/// RFC-0022 domain for the responder's persistent SSO X25519 key.
const SSO_ENCRYPTION_DOMAIN: &[u8] = b"sso";
/// Leave the product runtime one minute to receive and process the SSO response
/// before its 300-second remote-authority deadline expires.
#[cfg(not(target_arch = "wasm32"))]
const BULLETIN_AUTHORIZATION_WAIT: std::time::Duration = std::time::Duration::from_secs(240);
const BULLETIN_AUTHORIZATION_WAIT: core::time::Duration = core::time::Duration::from_secs(240);

/// Upper bound on remembered request ids for replay dedup within a serve loop.
/// A peer that holds the session open cannot grow this without bound; requests
Expand Down Expand Up @@ -152,19 +147,15 @@ pub(super) enum AllowanceAllocationError {
#[error("supported chains: {0}")]
SupportedChains(String),
/// Product-account key derivation failed.
#[cfg(not(target_arch = "wasm32"))]
#[error("{0}")]
ProductAccount(ProductAccountError),
/// Chain state, metadata, ring, slot, proof, or extrinsic allocation failed.
#[cfg(not(target_arch = "wasm32"))]
#[error("{0}")]
StatementAllowance(#[from] StatementAllowanceError),
/// Runtime service could not open the required Statement Store RPC client.
#[cfg(not(target_arch = "wasm32"))]
#[error("{0}")]
StatementStoreRpcClient(#[from] StatementStoreRpcClientError),
/// Runtime service could not open the required Bulletin RPC client.
#[cfg(not(target_arch = "wasm32"))]
#[error("{context}: {source}")]
ChainRpcClient {
/// Client context, naming which chain failed.
Expand All @@ -181,11 +172,9 @@ pub(super) enum AllowanceAllocationError {
resource: &'static str,
},
/// System time cannot be converted into a UNIX timestamp.
#[cfg(not(target_arch = "wasm32"))]
#[error("system clock before UNIX epoch")]
SystemClockBeforeUnixEpoch,
/// The signing account is not in any personhood ring.
#[cfg(not(target_arch = "wasm32"))]
#[error("signing account is not a personhood ring member; cannot grant {resource} allowance")]
MissingPersonhoodMembership {
/// Resource name.
Expand All @@ -199,7 +188,6 @@ impl From<AuthorityError> for AllowanceAllocationError {
}
}

#[cfg(not(target_arch = "wasm32"))]
impl From<ProductAccountError> for AllowanceAllocationError {
fn from(err: ProductAccountError) -> Self {
Self::ProductAccount(err)
Expand Down Expand Up @@ -1037,7 +1025,6 @@ pub(super) async fn allocate_statement_store_allowance(
Ok(allowance.secret.to_bytes().to_vec())
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn allocate_bulletin_allowance(
services: &Arc<RuntimeServices>,
signing_host: &SigningHost,
Expand Down Expand Up @@ -1270,22 +1257,13 @@ pub(super) async fn allocate_smart_contract_allowance(
Err(AllowanceAllocationError::NativeOnly { resource: "PGAS" })
}

#[cfg(target_arch = "wasm32")]
pub(super) async fn allocate_bulletin_allowance(
_services: &Arc<RuntimeServices>,
_signing_host: &SigningHost,
_product_id: &str,
_policy: OnExistingAllowancePolicy,
) -> Result<Vec<u8>, AllowanceAllocationError> {
Err(AllowanceAllocationError::NativeOnly {
resource: "Bulletin",
})
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn current_unix_secs() -> Result<u64, AllowanceAllocationError> {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(target_arch = "wasm32")]
use web_time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.map_err(|_| AllowanceAllocationError::SystemClockBeforeUnixEpoch)
}
Expand Down
13 changes: 11 additions & 2 deletions rust/crates/truapi-server/src/runtime/statement_allowance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
//! Mirrors how an iOS/web client obtains statement-store allowance from the real
//! People chain: build the `Resources.set_statement_store_account` call, prove
//! personhood ring membership with the caller's registry-selected ring-VRF key,
//! and submit the resulting unsigned General (v5) extrinsic. Native only
//! (needs the `verifiable` prover and live chain reads).
//! and submit the resulting unsigned General (v5) extrinsic. Compiles for
//! every target: the wasm host reaches both chains through its platform
//! connections, and the `verifiable` prover runs under wasm (the ring-VRF
//! product surface already ships it there). Only the PGAS claim and the
//! renewal loop remain native-only.

pub mod collection;
pub mod extension;
pub mod extrinsic;
#[cfg(not(target_arch = "wasm32"))]
pub mod pgas;
pub mod proof;
#[cfg(not(target_arch = "wasm32"))]
pub mod renewal;
pub mod ring;
pub mod rpc;
Expand All @@ -20,7 +25,10 @@ pub(crate) mod test_fixtures;

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};
#[cfg(target_arch = "wasm32")]
use web_time::{Duration, Instant};

use futures::FutureExt;
use parity_scale_codec::{Decode, Encode};
Expand Down Expand Up @@ -59,6 +67,7 @@ pub enum StatementAllowanceError {
#[error(transparent)]
Proof(#[from] proof::ProofError),
/// Asset Hub PGAS claim failed.
#[cfg(not(target_arch = "wasm32"))]
#[error(transparent)]
Pgas(#[from] pgas::PgasError),
/// Bulletin allowance polling timed out.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
//! Host-backed JSON-RPC helpers for statement-store allowance registration.
//! JSON-RPC helpers for People-chain and Bulletin allowance allocation.

use core::time::Duration;
use std::collections::HashMap;

use futures::{FutureExt, pin_mut};
use serde_json::{Value, json};
use subxt_rpcs::RpcClient as HostRpcClient;
use subxt_rpcs::client::{RpcClient as NativeRpcClient, RpcParams, rpc_params};
#[cfg(not(target_arch = "wasm32"))]
use subxt_rpcs::client::RpcClient as NativeRpcClient;
use subxt_rpcs::client::{RpcParams, rpc_params};
use thiserror::Error;

use super::StatementAllowanceError;

/// Timeout for an allowance registration extrinsic to reach a block.
const SUBMIT_TIMEOUT: Duration = Duration::from_secs(120);

/// Error from the native JSON-RPC surface used by allowance allocation.
/// Error from the JSON-RPC surface used by allowance allocation.
#[derive(Debug, Error)]
pub enum RpcError {
/// Opening a direct RPC URL failed.
Expand Down Expand Up @@ -69,6 +71,7 @@ pub struct RpcClient {

impl RpcClient {
/// Open a native JSON-RPC connection to `url`.
#[cfg(not(target_arch = "wasm32"))]
pub async fn connect(url: &str) -> Result<Self, StatementAllowanceError> {
let inner = NativeRpcClient::from_insecure_url(url)
.await
Expand Down
3 changes: 1 addition & 2 deletions rust/crates/truapi-server/src/runtime/statement_store_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ impl StatementStoreRpc {
}

/// Open a People-chain RPC client already scoped to its genesis hash, for
/// the native allowance paths that key the chain-context cache by it.
#[cfg(not(target_arch = "wasm32"))]
/// the allowance paths that key the chain-context cache by it.
pub(crate) async fn chain_client(
&self,
label: &'static str,
Expand Down