move fetch metadata to a separate crate subxt_utils_fetchmetadata (#1829)

* macros: feature-gate jsonrpsee/fetch metadata url

* make CI happy

* Update codegen/src/error.rs

* extract `fetch-metdata` to separate crate

* add missing license headers

* introduce subxt-utils crate

* add missing files

* codegen: remove unused hex crate

* fix test build

* move subxt_utils -> subxt_utils_fetchmetadata

* cargo fmt

* runtime-path -> runtime-metadata-path

* Update utils/fetch-metadata/src/lib.rs
This commit is contained in:
Niklas Adolfsson
2024-10-24 15:45:39 +02:00
committed by GitHub
parent f358a3864e
commit dc0795b3b9
20 changed files with 211 additions and 147 deletions
+3 -28
View File
@@ -11,9 +11,6 @@ use scale_typegen::TypegenError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CodegenError {
/// Cannot fetch the metadata bytes.
#[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing substrate-based metadata: {0}")]
Fetch(#[from] FetchMetadataError),
/// Cannot decode the metadata bytes.
#[error("Could not decode metadata, only V14 and V15 metadata are supported: {0}")]
Decode(#[from] codec::Error),
@@ -60,6 +57,9 @@ pub enum CodegenError {
/// Error when generating metadata from Wasm-runtime
#[error("Failed to generate metadata from wasm file. reason: {0}")]
Wasm(String),
/// Other error.
#[error("Other error: {0}")]
Other(String),
}
impl CodegenError {
@@ -81,28 +81,3 @@ impl CodegenError {
syn::Error::new(span, msg).into_compile_error()
}
}
/// Error attempting to load metadata.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FetchMetadataError {
/// Error decoding from a hex value.
#[error("Cannot decode hex value: {0}")]
DecodeError(#[from] hex::FromHexError),
/// Some SCALE codec error.
#[error("Cannot scale encode/decode value: {0}")]
CodecError(#[from] codec::Error),
/// JSON-RPC error fetching metadata.
#[cfg(feature = "fetch-metadata")]
#[error("Request error: {0}")]
RequestError(#[from] jsonrpsee::core::ClientError),
/// Failed IO when fetching from a file.
#[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata: {1}")]
Io(String, std::io::Error),
/// URL scheme is not http, https, ws or wss.
#[error("'{0}' not supported, supported URI schemes are http, https, ws or wss.")]
InvalidScheme(String),
/// Some other error.
#[error("Other error: {0}")]
Other(String),
}
-229
View File
@@ -1,229 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Helper methods for fetching metadata from a file or URL.
use crate::error::FetchMetadataError;
use codec::{Decode, Encode};
use jsonrpsee::{
async_client::ClientBuilder,
client_transport::ws::WsTransportClientBuilder,
core::client::{ClientT, Error},
http_client::HttpClientBuilder,
rpc_params,
};
use std::time::Duration;
pub use jsonrpsee::client_transport::ws::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))
}
}
}
}
/// Fetch metadata from a file.
pub fn fetch_metadata_from_file_blocking(
path: &std::path::Path,
) -> Result<Vec<u8>, FetchMetadataError> {
use std::io::Read;
let to_err = |err| FetchMetadataError::Io(path.to_string_lossy().into(), err);
let mut file = std::fs::File::open(path).map_err(to_err)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).map_err(to_err)?;
Ok(bytes)
}
/// Returns the metadata bytes from the provided URL, blocking the current thread.
pub fn fetch_metadata_from_url_blocking(
url: Url,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
tokio_block_on(fetch_metadata_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)
}
/// Returns the metadata bytes from the provided URL.
pub async fn fetch_metadata_from_url(
url: Url,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let bytes = match url.scheme() {
"http" | "https" => fetch_metadata_http(url, version).await,
"ws" | "wss" => fetch_metadata_ws(url, version).await,
invalid_scheme => Err(FetchMetadataError::InvalidScheme(invalid_scheme.to_owned())),
}?;
Ok(bytes)
}
async fn fetch_metadata_ws(
url: Url,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let (sender, receiver) = WsTransportClientBuilder::default()
.build(url)
.await
.map_err(|e| Error::Transport(e.into()))?;
let client = ClientBuilder::default()
.request_timeout(Duration::from_secs(180))
.max_buffer_capacity_per_subscription(4096)
.build_with_tokio(sender, receiver);
fetch_metadata(client, version).await
}
async fn fetch_metadata_http(
url: Url,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
let client = HttpClientBuilder::default()
.request_timeout(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>, FetchMetadataError> {
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
// Fetch metadata using the "new" state_call interface
async fn fetch_inner(
client: &impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
// Look up supported versions:
let supported_versions: Vec<u32> = {
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[..])?
};
// 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(|| {
FetchMetadataError::Other("No valid metadata versions returned".to_string())
})?,
MetadataVersion::Unstable => {
if supported_versions.contains(&UNSTABLE_METADATA_VERSION) {
UNSTABLE_METADATA_VERSION
} else {
return Err(FetchMetadataError::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(FetchMetadataError::Other(format!(
"The node does not have 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(FetchMetadataError::Other(format!(
"The node does not have version {version} available"
)));
};
Ok(metadata.0)
}
// Fetch metadata using the "old" state_call interface
async fn fetch_inner_legacy(
client: &impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
// If the user specifically asks for anything other than version 14 or "latest", error.
if !matches!(
version,
MetadataVersion::Latest | MetadataVersion::Version(14)
) {
return Err(FetchMetadataError::Other(
"The node can only return version 14 metadata using the legacy API but you've asked for something else"
.to_string(),
));
}
// 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)
}
// Fetch using the new interface, falling back to trying old one if there's an error.
match fetch_inner(&client, version).await {
Ok(s) => Ok(s),
Err(_) => fetch_inner_legacy(&client, version).await,
}
}
+1 -8
View File
@@ -6,20 +6,13 @@
//! This is used by the `#[subxt]` macro and `subxt codegen` CLI command, but can also
//! be used directly if preferable.
#![deny(unused_crate_dependencies, missing_docs)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod api;
pub mod error;
mod ir;
// These should probably be in a separate crate; they are used by the
// macro and CLI tool, so they only live here because this is a common
// crate that both depend on.
#[cfg(feature = "fetch-metadata")]
#[cfg_attr(docsrs, doc(cfg(feature = "fetch-metadata")))]
pub mod fetch_metadata;
#[cfg(feature = "web")]
use getrandom as _;