subxt_utils_fetchmetadata/
url.rs1use crate::Error;
8use codec::{Decode, Encode};
9use jsonrpsee::{
10 core::client::ClientT, http_client::HttpClientBuilder, rpc_params, ws_client::WsClientBuilder,
11};
12
13pub use url::Url;
14
15#[derive(Default, Debug, Clone, Copy)]
17pub enum MetadataVersion {
18 #[default]
20 Latest,
21 Version(u32),
23 Unstable,
25}
26
27impl std::str::FromStr for MetadataVersion {
29 type Err = String;
30
31 fn from_str(input: &str) -> Result<Self, Self::Err> {
32 match input {
33 "unstable" => Ok(MetadataVersion::Unstable),
34 "latest" => Ok(MetadataVersion::Latest),
35 version => {
36 let num: u32 = version
37 .parse()
38 .map_err(|_| format!("Invalid metadata version specified {version:?}"))?;
39
40 Ok(MetadataVersion::Version(num))
41 }
42 }
43 }
44}
45
46pub async fn from_url(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
48 let bytes = match url.scheme() {
49 "http" | "https" => fetch_metadata_http(url, version).await,
50 "ws" | "wss" => fetch_metadata_ws(url, version).await,
51 invalid_scheme => Err(Error::InvalidScheme(invalid_scheme.to_owned())),
52 }?;
53
54 Ok(bytes)
55}
56
57pub fn from_url_blocking(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
59 tokio_block_on(from_url(url, version))
60}
61
62fn tokio_block_on<T, Fut: std::future::Future<Output = T>>(fut: Fut) -> T {
64 tokio::runtime::Builder::new_multi_thread()
65 .enable_all()
66 .build()
67 .unwrap()
68 .block_on(fut)
69}
70
71async fn fetch_metadata_ws(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
72 let client = WsClientBuilder::default()
73 .request_timeout(std::time::Duration::from_secs(180))
74 .max_buffer_capacity_per_subscription(4096)
75 .build(url)
76 .await?;
77
78 fetch_metadata(client, version).await
79}
80
81async fn fetch_metadata_http(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
82 let client = HttpClientBuilder::default()
83 .request_timeout(std::time::Duration::from_secs(180))
84 .build(url)?;
85
86 fetch_metadata(client, version).await
87}
88
89async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Result<Vec<u8>, Error> {
91 const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
92
93 async fn fetch_available_versions(
95 client: &impl ClientT,
96 ) -> Result<Vec<u32>, Error> {
97 let res: String = client
98 .request("state_call", rpc_params!["Metadata_metadata_versions", "0x"])
99 .await?;
100 let raw_bytes = hex::decode(res.trim_start_matches("0x"))?;
101 Decode::decode(&mut &raw_bytes[..]).map_err(Into::into)
102 }
103
104 async fn fetch_inner(
106 client: &impl ClientT,
107 version: MetadataVersion,
108 supported_versions: Vec<u32>,
109 ) -> Result<Vec<u8>, Error> {
110 let version = match version {
112 MetadataVersion::Latest => *supported_versions
113 .iter()
114 .filter(|&&v| v != UNSTABLE_METADATA_VERSION)
115 .max()
116 .ok_or_else(|| Error::Other("No valid metadata versions returned".to_string()))?,
117 MetadataVersion::Unstable => {
118 if supported_versions.contains(&UNSTABLE_METADATA_VERSION) {
119 UNSTABLE_METADATA_VERSION
120 } else {
121 return Err(Error::Other(
122 "The node does not have an unstable metadata version available".to_string(),
123 ));
124 }
125 }
126 MetadataVersion::Version(version) => {
127 if supported_versions.contains(&version) {
128 version
129 } else {
130 return Err(Error::Other(format!(
131 "The node does not have metadata version {version} available"
132 )));
133 }
134 }
135 };
136
137 let bytes = version.encode();
138 let version: String = format!("0x{}", hex::encode(&bytes));
139
140 let metadata_string: String = client
142 .request(
143 "state_call",
144 rpc_params!["Metadata_metadata_at_version", &version],
145 )
146 .await?;
147 let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
149 let metadata: Option<frame_metadata::OpaqueMetadata> =
150 Decode::decode(&mut &metadata_bytes[..])?;
151 let Some(metadata) = metadata else {
152 return Err(Error::Other(format!(
153 "The node does not have metadata version {version} available"
154 )));
155 };
156 Ok(metadata.0)
157 }
158
159 async fn fetch_inner_legacy(
161 client: &impl ClientT,
162 ) -> Result<Vec<u8>, Error> {
163 let metadata_string: String = client
165 .request("state_call", rpc_params!["Metadata_metadata", "0x"])
166 .await?;
167
168 let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
170 let metadata: frame_metadata::OpaqueMetadata = Decode::decode(&mut &metadata_bytes[..])?;
171 Ok(metadata.0)
172 }
173
174 match fetch_available_versions(&client).await {
175 Ok(supported_versions) => {
176 fetch_inner(&client, version, supported_versions).await
177 },
178 Err(e) => {
179 if matches!(version, MetadataVersion::Version(14) | MetadataVersion::Latest) {
183 fetch_inner_legacy(&client).await
184 } else {
185 Err(e)
186 }
187 }
188 }
189}