diff --git a/cli/src/commands/compatibility.rs b/cli/src/commands/compatibility.rs index 70a03a0390..afea30b56c 100644 --- a/cli/src/commands/compatibility.rs +++ b/cli/src/commands/compatibility.rs @@ -137,7 +137,7 @@ async fn fetch_runtime_metadata( url: Url, version: MetadataVersion, ) -> color_eyre::Result { - let bytes = subxt_utils_fetchmetadata::from_url(url, version).await?; + let bytes = subxt_utils_fetchmetadata::from_url(url, version, None).await?; let metadata = Metadata::decode(&mut &bytes[..])?; Ok(metadata) } diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 8d083fb0ac..08fb3ab2a9 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -34,6 +34,11 @@ pub struct FileOrUrl { /// Defaults to asking for the latest stable metadata version. #[clap(long)] pub version: Option, + /// Block hash (hex encoded) to attempt to fetch the metadata from. + /// If not provided, we default to the latest finalized block. + /// Non-archive nodes will be unable to provide metadata from old blocks. + #[clap(long)] + pub at_block: Option, } impl FromStr for FileOrUrl { @@ -45,6 +50,7 @@ impl FromStr for FileOrUrl { url: None, file: Some(path), version: None, + at_block: None, }) } else { Url::parse(s) @@ -53,6 +59,7 @@ impl FromStr for FileOrUrl { url: Some(uri), file: None, version: None, + at_block: None, }) } } @@ -87,19 +94,23 @@ impl FromStr for PathOrStdIn { impl FileOrUrl { /// Fetch the metadata bytes. pub async fn fetch(&self) -> color_eyre::Result> { - match (&self.file, &self.url, self.version) { + match (&self.file, &self.url, self.version, &self.at_block) { // Can't provide both --file and --url - (Some(_), Some(_), _) => { + (Some(_), Some(_), _, _) => { bail!("specify one of `--url` or `--file` but not both") } + // --at-block must be provided with --url + (Some(_path_or_stdin), _, _, Some(_at_block)) => { + bail!("`--at-block` can only be used with `--url`") + } // Load from --file path - (Some(PathOrStdIn::Path(path)), None, None) => { + (Some(PathOrStdIn::Path(path)), None, None, None) => { let mut file = fs::File::open(path)?; let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; Ok(bytes) } - (Some(PathOrStdIn::StdIn), None, None) => { + (Some(PathOrStdIn::StdIn), None, None, None) => { let reader = std::io::BufReader::new(std::io::stdin()); let res = reader.bytes().collect::, _>>(); @@ -109,7 +120,7 @@ impl FileOrUrl { } } // Cannot load the metadata from the file and specify a version to fetch. - (Some(_), None, Some(_)) => { + (Some(_), None, Some(_), None) => { // Note: we could provide the ability to convert between metadata versions // but that would be involved because we'd need to convert // from each metadata to the latest one and from the @@ -117,13 +128,19 @@ impl FileOrUrl { bail!("`--file` is incompatible with `--version`") } // Fetch from --url - (None, Some(uri), version) => { - Ok(fetch_metadata::from_url(uri.clone(), version.unwrap_or_default()).await?) - } + (None, Some(uri), version, at_block) => Ok(fetch_metadata::from_url( + uri.clone(), + version.unwrap_or_default(), + at_block.as_deref(), + ) + .await?), // Default if neither is provided; fetch from local url - (None, None, version) => { + (None, None, version, at_block) => { let url = Url::parse("ws://localhost:9944").expect("Valid URL; qed"); - Ok(fetch_metadata::from_url(url, version.unwrap_or_default()).await?) + Ok( + fetch_metadata::from_url(url, version.unwrap_or_default(), at_block.as_deref()) + .await?, + ) } } } @@ -336,7 +353,8 @@ mod tests { Ok(FileOrUrl { url: None, file: Some(PathOrStdIn::StdIn), - version: None + version: None, + at_block: None, }) ),); @@ -345,7 +363,8 @@ mod tests { Ok(FileOrUrl { url: None, file: Some(PathOrStdIn::StdIn), - version: None + version: None, + at_block: None, }) ),); @@ -354,7 +373,8 @@ mod tests { Ok(FileOrUrl { url: None, file: Some(PathOrStdIn::Path(_)), - version: None + version: None, + at_block: None, }) ),); @@ -365,7 +385,8 @@ mod tests { Ok(FileOrUrl { url: Some(_), file: None, - version: None + version: None, + at_block: None, }) )); } diff --git a/macro/src/lib.rs b/macro/src/lib.rs index c3e00da1b9..f870289d5c 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -263,7 +263,7 @@ fn fetch_metadata(args: &RuntimeMetadataArgs) -> Result MetadataVersion::Latest, }; - from_url_blocking(url, version) + from_url_blocking(url, version, None) .map_err(|e| CodegenError::Other(e.to_string())) .and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into)) .map_err(|e| e.into_compile_error())? diff --git a/utils/fetch-metadata/src/url.rs b/utils/fetch-metadata/src/url.rs index 5c9ffd6136..cb8c855a0d 100644 --- a/utils/fetch-metadata/src/url.rs +++ b/utils/fetch-metadata/src/url.rs @@ -5,7 +5,7 @@ //! Fetch metadata from a URL. use crate::Error; -use codec::{Decode, Encode}; +use codec::{Decode, Encode}; use jsonrpsee::{ core::client::ClientT, http_client::HttpClientBuilder, rpc_params, ws_client::WsClientBuilder, }; @@ -44,10 +44,10 @@ impl std::str::FromStr for MetadataVersion { } /// Returns the metadata bytes from the provided URL. -pub async fn from_url(url: Url, version: MetadataVersion) -> Result, Error> { +pub async fn from_url(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result, Error> { let bytes = match url.scheme() { - "http" | "https" => fetch_metadata_http(url, version).await, - "ws" | "wss" => fetch_metadata_ws(url, version).await, + "http" | "https" => fetch_metadata_http(url, version, at_block_hash).await, + "ws" | "wss" => fetch_metadata_ws(url, version, at_block_hash).await, invalid_scheme => Err(Error::InvalidScheme(invalid_scheme.to_owned())), }?; @@ -55,8 +55,8 @@ pub async fn from_url(url: Url, version: MetadataVersion) -> Result, Err } /// Returns the metadata bytes from the provided URL, blocking the current thread. -pub fn from_url_blocking(url: Url, version: MetadataVersion) -> Result, Error> { - tokio_block_on(from_url(url, version)) +pub fn from_url_blocking(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result, Error> { + tokio_block_on(from_url(url, version, at_block_hash)) } // Block on some tokio runtime for sync contexts @@ -68,34 +68,40 @@ fn tokio_block_on>(fut: Fut) -> T { .block_on(fut) } -async fn fetch_metadata_ws(url: Url, version: MetadataVersion) -> Result, Error> { +async fn fetch_metadata_ws(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result, 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 + fetch_metadata(client, version, at_block_hash).await } -async fn fetch_metadata_http(url: Url, version: MetadataVersion) -> Result, Error> { +async fn fetch_metadata_http(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result, Error> { let client = HttpClientBuilder::default() .request_timeout(std::time::Duration::from_secs(180)) .build(url)?; - fetch_metadata(client, version).await + fetch_metadata(client, version, at_block_hash).await } /// The innermost call to fetch metadata: -async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Result, Error> { +async fn fetch_metadata(client: impl ClientT, version: MetadataVersion, at_block_hash: Option<&str>) -> Result, Error> { const UNSTABLE_METADATA_VERSION: u32 = u32::MAX; + // Ensure always 0x prefix. + let at_block_hash = at_block_hash + .map(|hash| format!("0x{}", hash.strip_prefix("0x").unwrap_or(hash))); + let at_block_hash = at_block_hash.as_deref(); + // Fetch available metadata versions. If error, revert to legacy metadata code. async fn fetch_available_versions( client: &impl ClientT, + at_block_hash: Option<&str>, ) -> Result, Error> { let res: String = client - .request("state_call", rpc_params!["Metadata_metadata_versions", "0x"]) + .request("state_call", rpc_params!["Metadata_metadata_versions", "0x", at_block_hash]) .await?; let raw_bytes = hex::decode(res.trim_start_matches("0x"))?; Decode::decode(&mut &raw_bytes[..]).map_err(Into::into) @@ -106,6 +112,7 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul client: &impl ClientT, version: MetadataVersion, supported_versions: Vec, + at_block_hash: Option<&str>, ) -> Result, Error> { // Return the version the user wants if it's supported: let version = match version { @@ -141,7 +148,7 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul let metadata_string: String = client .request( "state_call", - rpc_params!["Metadata_metadata_at_version", &version], + rpc_params!["Metadata_metadata_at_version", &version, at_block_hash], ) .await?; // Decode the metadata. @@ -159,10 +166,11 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul // Fetch metadata using the "old" state_call interface async fn fetch_inner_legacy( client: &impl ClientT, + at_block_hash: Option<&str>, ) -> Result, Error> { // Fetch the metadata. let metadata_string: String = client - .request("state_call", rpc_params!["Metadata_metadata", "0x"]) + .request("state_call", rpc_params!["Metadata_metadata", "0x", at_block_hash]) .await?; // Decode the metadata. @@ -171,16 +179,16 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul Ok(metadata.0) } - match fetch_available_versions(&client).await { + match fetch_available_versions(&client, at_block_hash).await { Ok(supported_versions) => { - fetch_inner(&client, version, supported_versions).await + fetch_inner(&client, version, supported_versions, at_block_hash).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 + fetch_inner_legacy(&client, at_block_hash).await } else { Err(e) }