Files
pezkuwi-subxt/utils/fetch-metadata/src/url.rs
T
puzzle-rusher e892802baf Clippy fixes (#2033)
* clippy fixed

* wasm clippy

* clippy fixes

* post clippy fmt

* last clippy fix
2025-07-04 21:21:31 +01:00

190 lines
6.6 KiB
Rust

// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Fetch metadata from a URL.
use crate::Error;
use codec::{Decode, Encode};
use jsonrpsee::{
core::client::ClientT, http_client::HttpClientBuilder, rpc_params, ws_client::WsClientBuilder,
};
pub use url::Url;
/// The metadata version that is fetched from the node.
#[derive(Default, Debug, Clone, Copy)]
pub enum MetadataVersion {
/// Latest stable version of the metadata.
#[default]
Latest,
/// Fetch a specified version of the metadata.
Version(u32),
/// Latest unstable version of the metadata.
Unstable,
}
// Note: Implementation needed for the CLI tool.
impl std::str::FromStr for MetadataVersion {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"unstable" => Ok(MetadataVersion::Unstable),
"latest" => Ok(MetadataVersion::Latest),
version => {
let num: u32 = version
.parse()
.map_err(|_| format!("Invalid metadata version specified {version:?}"))?;
Ok(MetadataVersion::Version(num))
}
}
}
}
/// Returns the metadata bytes from the provided URL.
pub async fn from_url(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
let bytes = match url.scheme() {
"http" | "https" => fetch_metadata_http(url, version).await,
"ws" | "wss" => fetch_metadata_ws(url, version).await,
invalid_scheme => Err(Error::InvalidScheme(invalid_scheme.to_owned())),
}?;
Ok(bytes)
}
/// Returns the metadata bytes from the provided URL, blocking the current thread.
pub fn from_url_blocking(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
tokio_block_on(from_url(url, version))
}
// Block on some tokio runtime for sync contexts
fn tokio_block_on<T, Fut: std::future::Future<Output = T>>(fut: Fut) -> T {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(fut)
}
async fn fetch_metadata_ws(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
let client = WsClientBuilder::default()
.request_timeout(std::time::Duration::from_secs(180))
.max_buffer_capacity_per_subscription(4096)
.build(url)
.await?;
fetch_metadata(client, version).await
}
async fn fetch_metadata_http(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
let client = HttpClientBuilder::default()
.request_timeout(std::time::Duration::from_secs(180))
.build(url)?;
fetch_metadata(client, version).await
}
/// The innermost call to fetch metadata:
async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Result<Vec<u8>, Error> {
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
// Fetch available metadata versions. If error, revert to legacy metadata code.
async fn fetch_available_versions(
client: &impl ClientT,
) -> Result<Vec<u32>, Error> {
let res: String = client
.request("state_call", rpc_params!["Metadata_metadata_versions", "0x"])
.await?;
let raw_bytes = hex::decode(res.trim_start_matches("0x"))?;
Decode::decode(&mut &raw_bytes[..]).map_err(Into::into)
}
// Fetch metadata using the "new" state_call interface
async fn fetch_inner(
client: &impl ClientT,
version: MetadataVersion,
supported_versions: Vec<u32>,
) -> Result<Vec<u8>, Error> {
// Return the version the user wants if it's supported:
let version = match version {
MetadataVersion::Latest => *supported_versions
.iter()
.filter(|&&v| v != UNSTABLE_METADATA_VERSION)
.max()
.ok_or_else(|| Error::Other("No valid metadata versions returned".to_string()))?,
MetadataVersion::Unstable => {
if supported_versions.contains(&UNSTABLE_METADATA_VERSION) {
UNSTABLE_METADATA_VERSION
} else {
return Err(Error::Other(
"The node does not have an unstable metadata version available".to_string(),
));
}
}
MetadataVersion::Version(version) => {
if supported_versions.contains(&version) {
version
} else {
return Err(Error::Other(format!(
"The node does not have metadata version {version} available"
)));
}
}
};
let bytes = version.encode();
let version: String = format!("0x{}", hex::encode(&bytes));
// Fetch the metadata at that version:
let metadata_string: String = client
.request(
"state_call",
rpc_params!["Metadata_metadata_at_version", &version],
)
.await?;
// Decode the metadata.
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
let metadata: Option<frame_metadata::OpaqueMetadata> =
Decode::decode(&mut &metadata_bytes[..])?;
let Some(metadata) = metadata else {
return Err(Error::Other(format!(
"The node does not have metadata version {version} available"
)));
};
Ok(metadata.0)
}
// Fetch metadata using the "old" state_call interface
async fn fetch_inner_legacy(
client: &impl ClientT,
) -> Result<Vec<u8>, Error> {
// Fetch the metadata.
let metadata_string: String = client
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
.await?;
// Decode the metadata.
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
let metadata: frame_metadata::OpaqueMetadata = Decode::decode(&mut &metadata_bytes[..])?;
Ok(metadata.0)
}
match fetch_available_versions(&client).await {
Ok(supported_versions) => {
fetch_inner(&client, version, supported_versions).await
},
Err(e) => {
// The "new" interface failed. if the user is asking for V14 or the "latest"
// metadata then try the legacy interface instead. Else, just return the
// reason for failure.
if matches!(version, MetadataVersion::Version(14) | MetadataVersion::Latest) {
fetch_inner_legacy(&client).await
} else {
Err(e)
}
}
}
}