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: 4 additions & 1 deletion js/packages/truapi-host/src/test-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export function makeHostCallbacks(
devicePermission: async () => ({ granted: false }),
remotePermission: async () => ({ granted: false }),
},
features: { featureSupported: async () => ({ supported: false }) },
features: {
featureSupported: async () => ({ supported: false }),
supportedChains: async () => ({ network: "paseo", chains: [] }),
},
productStorage: {
read: async () => undefined,
write: async () => {},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions rust/crates/truapi-codegen/tests/golden/host-callbacks.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions rust/crates/truapi-codegen/tests/golden/wasm_bridge.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions rust/crates/truapi-codegen/tests/golden/worker-callbacks.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions rust/crates/truapi-host-cli/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,12 @@ impl Features for CliPlatform {
) -> Result<api::HostFeatureSupportedResponse, api::GenericError> {
Ok(api::HostFeatureSupportedResponse { supported: false })
}

async fn supported_chains(&self) -> Result<truapi_platform::HostChainSet, api::GenericError> {
Err(api::GenericError {
reason: "the CLI host serves no product chains".to_string(),
})
}
}

impl truapi_platform::AuthPresenter for CliPlatform {
Expand Down
46 changes: 36 additions & 10 deletions rust/crates/truapi-platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ uniffi::use_remote_type!(truapi::Bytes32);

use truapi::Bytes32;
use truapi::latest::{
AllocatableResource, GenericError, HostChatCreateRoomError, HostChatCreateRoomRequest,
HostChatCreateRoomResponse, HostChatListSubscribeItem, HostChatPostMessageError,
HostChatPostMessageRequest, HostChatPostMessageResponse, HostDevicePermissionRequest,
HostDevicePermissionResponse, HostFeatureSupportedRequest, HostFeatureSupportedResponse,
HostLocalStorageReadError, HostNavigateToError, HostPushNotificationRequest,
HostPushNotificationResponse, HostSignPayloadRequest, HostSignPayloadWithLegacyAccountRequest,
HostSignRawRequest, HostSignRawWithLegacyAccountRequest, LegacyAccountTxPayload,
NotificationId, ProductAccountId, ProductAccountTxPayload, ProductProofContext,
RemotePermission, RemotePermissionRequest, RemotePermissionResponse, RingLocation,
ThemeVariant,
AllocatableResource, ChainIdentifier, GenericError, HostChatCreateRoomError,
HostChatCreateRoomRequest, HostChatCreateRoomResponse, HostChatListSubscribeItem,
HostChatPostMessageError, HostChatPostMessageRequest, HostChatPostMessageResponse,
HostDevicePermissionRequest, HostDevicePermissionResponse, HostFeatureSupportedRequest,
HostFeatureSupportedResponse, HostLocalStorageReadError, HostNavigateToError,
HostPushNotificationRequest, HostPushNotificationResponse, HostSignPayloadRequest,
HostSignPayloadWithLegacyAccountRequest, HostSignRawRequest,
HostSignRawWithLegacyAccountRequest, LegacyAccountTxPayload, NotificationId, ProductAccountId,
ProductAccountTxPayload, ProductProofContext, RemotePermission, RemotePermissionRequest,
RemotePermissionResponse, RingLocation, ThemeVariant,
};
use truapi::v01::HostAccountSignVrfRequest;
use url::Url;
Expand Down Expand Up @@ -502,6 +502,27 @@ pub trait PairingHostAdmin: Send + Sync {
fn notify_session_store_changed(&self);
}

/// One chain a host serves: a protocol chain role mapped to the concrete
/// chain of the host's configured environment.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct HostChainEntry {
/// Protocol role this entry answers for.
pub identifier: ChainIdentifier,
/// Genesis hash identifying the chain in all chain-scoped calls.
pub genesis_hash: Bytes32,
}

/// The chain set a host serves: its environment plus one entry per chain role.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct HostChainSet {
/// Ecosystem the host is configured for, e.g. "polkadot", "paseo".
pub network: String,
/// Complete set of chains available through this host.
pub chains: Vec<HostChainEntry>,
}

/// Feature-support probing. The host answers whether it can service a given
/// capability (currently scoped to per-chain support).
#[async_trait]
Expand All @@ -511,6 +532,11 @@ pub trait Features: Send + Sync {
&self,
request: HostFeatureSupportedRequest,
) -> Result<HostFeatureSupportedResponse, GenericError>;

/// Enumerate the chains this host serves (RFC 0026). The returned set must
/// match exactly what [`ChainProvider::connect`] will accept; the core
/// resolves `get_chain_info` requests against it.
async fn supported_chains(&self) -> Result<HostChainSet, GenericError>;
}

/// JSON-RPC provider factory for chain access.
Expand Down
96 changes: 92 additions & 4 deletions rust/crates/truapi-server/src/host_logic/features.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//! Feature-detection delegation.
//!
//! `feature_supported` is a platform syscall: each host owns the set of
//! chains it can service. This module is a thin shim that forwards the
//! request through to [`truapi_platform::Features`].
//! `feature_supported` and `supported_chains` are platform syscalls: each
//! host owns the set of chains it can service. This module is a thin shim
//! that forwards through to [`truapi_platform::Features`], plus the in-core
//! RFC-0026 resolution that answers `get_chain_info` from the host's chain
//! set so per-request semantics (ordering, `NotSupported`) stay core-owned.

use truapi::latest::{RemoteChainInfoError, RemoteChainInfoRequest, RemoteChainInfoResponse};
use truapi::v01::{GenericError, HostFeatureSupportedRequest, HostFeatureSupportedResponse};
use truapi_platform::Features;
use truapi_platform::{Features, HostChainSet};

/// Forward a feature-support query to the platform implementation.
pub async fn feature_supported<P: Features + ?Sized>(
Expand All @@ -15,9 +18,52 @@ pub async fn feature_supported<P: Features + ?Sized>(
platform.feature_supported(request).await
}

/// Fetch the host's chain set from the platform implementation.
pub async fn supported_chains<P: Features + ?Sized>(
platform: &P,
) -> Result<HostChainSet, GenericError> {
platform.supported_chains().await
}

/// Resolve a `get_chain_info` request against the host's chain set: the
/// requested identifier's genesis hash plus the host's network, echoing the
/// identifier, or `NotSupported` when the host does not serve it.
pub fn chain_info(
set: &HostChainSet,
request: &RemoteChainInfoRequest,
) -> Result<RemoteChainInfoResponse, RemoteChainInfoError> {
set.chains
.iter()
.find(|entry| entry.identifier == request.chain)
.map(|entry| RemoteChainInfoResponse {
network: set.network.clone(),
chain: entry.identifier,
genesis_hash: entry.genesis_hash,
})
.ok_or(RemoteChainInfoError::NotSupported)
}

#[cfg(test)]
mod tests {
use super::*;
use truapi::latest::ChainIdentifier;
use truapi_platform::HostChainEntry;

fn paseo_set() -> HostChainSet {
HostChainSet {
network: "paseo".to_string(),
chains: vec![
HostChainEntry {
identifier: ChainIdentifier::AssetHub,
genesis_hash: [0xaa; 32],
},
HostChainEntry {
identifier: ChainIdentifier::People,
genesis_hash: [0xbb; 32],
},
],
}
}

struct AlwaysSupported;

Expand All @@ -30,6 +76,10 @@ mod tests {
assert!(matches!(request, HostFeatureSupportedRequest::Chain { .. }));
Ok(HostFeatureSupportedResponse { supported: true })
}

async fn supported_chains(&self) -> Result<HostChainSet, GenericError> {
Ok(paseo_set())
}
}

struct AlwaysUnsupported;
Expand All @@ -43,6 +93,12 @@ mod tests {
assert!(matches!(request, HostFeatureSupportedRequest::Chain { .. }));
Ok(HostFeatureSupportedResponse { supported: false })
}

async fn supported_chains(&self) -> Result<HostChainSet, GenericError> {
Err(GenericError {
reason: "no chains".to_string(),
})
}
}

fn req() -> HostFeatureSupportedRequest {
Expand All @@ -63,4 +119,36 @@ mod tests {
futures::executor::block_on(feature_supported(&AlwaysUnsupported, req())).unwrap();
assert!(!resp.supported);
}

#[test]
fn delegates_supported_chains_to_platform() {
let set = futures::executor::block_on(supported_chains(&AlwaysSupported)).unwrap();
assert_eq!(set, paseo_set());
}

#[test]
fn surfaces_supported_chains_platform_error() {
let err = futures::executor::block_on(supported_chains(&AlwaysUnsupported)).unwrap_err();
assert_eq!(err.reason, "no chains");
}

#[test]
fn resolves_identifier_and_echoes_it() {
let request = RemoteChainInfoRequest {
chain: ChainIdentifier::People,
};
let response = chain_info(&paseo_set(), &request).unwrap();
assert_eq!(response.network, "paseo");
assert_eq!(response.chain, ChainIdentifier::People);
assert_eq!(response.genesis_hash, [0xbb; 32]);
}

#[test]
fn unserved_identifier_is_not_supported() {
let request = RemoteChainInfoRequest {
chain: ChainIdentifier::Bulletin,
};
let err = chain_info(&paseo_set(), &request).unwrap_err();
assert_eq!(err, RemoteChainInfoError::NotSupported);
}
}
Loading