Skip to content

Commit a79c531

Browse files
authored
Add --at-block option to CLI tool to download metadata at a specific block (#2079)
* Add --at-block option to CLI tool to download metadata at a specific block; useful for debugging * clippy
1 parent 07ed8ba commit a79c531

File tree

4 files changed

+62
-33
lines changed

4 files changed

+62
-33
lines changed

cli/src/commands/compatibility.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async fn fetch_runtime_metadata(
137137
url: Url,
138138
version: MetadataVersion,
139139
) -> color_eyre::Result<Metadata> {
140-
let bytes = subxt_utils_fetchmetadata::from_url(url, version).await?;
140+
let bytes = subxt_utils_fetchmetadata::from_url(url, version, None).await?;
141141
let metadata = Metadata::decode(&mut &bytes[..])?;
142142
Ok(metadata)
143143
}

cli/src/utils.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ pub struct FileOrUrl {
3434
/// Defaults to asking for the latest stable metadata version.
3535
#[clap(long)]
3636
pub version: Option<MetadataVersion>,
37+
/// Block hash (hex encoded) to attempt to fetch the metadata from.
38+
/// If not provided, we default to the latest finalized block.
39+
/// Non-archive nodes will be unable to provide metadata from old blocks.
40+
#[clap(long)]
41+
pub at_block: Option<String>,
3742
}
3843

3944
impl FromStr for FileOrUrl {
@@ -45,6 +50,7 @@ impl FromStr for FileOrUrl {
4550
url: None,
4651
file: Some(path),
4752
version: None,
53+
at_block: None,
4854
})
4955
} else {
5056
Url::parse(s)
@@ -53,6 +59,7 @@ impl FromStr for FileOrUrl {
5359
url: Some(uri),
5460
file: None,
5561
version: None,
62+
at_block: None,
5663
})
5764
}
5865
}
@@ -87,19 +94,23 @@ impl FromStr for PathOrStdIn {
8794
impl FileOrUrl {
8895
/// Fetch the metadata bytes.
8996
pub async fn fetch(&self) -> color_eyre::Result<Vec<u8>> {
90-
match (&self.file, &self.url, self.version) {
97+
match (&self.file, &self.url, self.version, &self.at_block) {
9198
// Can't provide both --file and --url
92-
(Some(_), Some(_), _) => {
99+
(Some(_), Some(_), _, _) => {
93100
bail!("specify one of `--url` or `--file` but not both")
94101
}
102+
// --at-block must be provided with --url
103+
(Some(_path_or_stdin), _, _, Some(_at_block)) => {
104+
bail!("`--at-block` can only be used with `--url`")
105+
}
95106
// Load from --file path
96-
(Some(PathOrStdIn::Path(path)), None, None) => {
107+
(Some(PathOrStdIn::Path(path)), None, None, None) => {
97108
let mut file = fs::File::open(path)?;
98109
let mut bytes = Vec::new();
99110
file.read_to_end(&mut bytes)?;
100111
Ok(bytes)
101112
}
102-
(Some(PathOrStdIn::StdIn), None, None) => {
113+
(Some(PathOrStdIn::StdIn), None, None, None) => {
103114
let reader = std::io::BufReader::new(std::io::stdin());
104115
let res = reader.bytes().collect::<Result<Vec<u8>, _>>();
105116

@@ -109,21 +120,27 @@ impl FileOrUrl {
109120
}
110121
}
111122
// Cannot load the metadata from the file and specify a version to fetch.
112-
(Some(_), None, Some(_)) => {
123+
(Some(_), None, Some(_), None) => {
113124
// Note: we could provide the ability to convert between metadata versions
114125
// but that would be involved because we'd need to convert
115126
// from each metadata to the latest one and from the
116127
// latest one to each metadata version. For now, disable the conversion.
117128
bail!("`--file` is incompatible with `--version`")
118129
}
119130
// Fetch from --url
120-
(None, Some(uri), version) => {
121-
Ok(fetch_metadata::from_url(uri.clone(), version.unwrap_or_default()).await?)
122-
}
131+
(None, Some(uri), version, at_block) => Ok(fetch_metadata::from_url(
132+
uri.clone(),
133+
version.unwrap_or_default(),
134+
at_block.as_deref(),
135+
)
136+
.await?),
123137
// Default if neither is provided; fetch from local url
124-
(None, None, version) => {
138+
(None, None, version, at_block) => {
125139
let url = Url::parse("ws://localhost:9944").expect("Valid URL; qed");
126-
Ok(fetch_metadata::from_url(url, version.unwrap_or_default()).await?)
140+
Ok(
141+
fetch_metadata::from_url(url, version.unwrap_or_default(), at_block.as_deref())
142+
.await?,
143+
)
127144
}
128145
}
129146
}
@@ -336,7 +353,8 @@ mod tests {
336353
Ok(FileOrUrl {
337354
url: None,
338355
file: Some(PathOrStdIn::StdIn),
339-
version: None
356+
version: None,
357+
at_block: None,
340358
})
341359
),);
342360

@@ -345,7 +363,8 @@ mod tests {
345363
Ok(FileOrUrl {
346364
url: None,
347365
file: Some(PathOrStdIn::StdIn),
348-
version: None
366+
version: None,
367+
at_block: None,
349368
})
350369
),);
351370

@@ -354,7 +373,8 @@ mod tests {
354373
Ok(FileOrUrl {
355374
url: None,
356375
file: Some(PathOrStdIn::Path(_)),
357-
version: None
376+
version: None,
377+
at_block: None,
358378
})
359379
),);
360380

@@ -365,7 +385,8 @@ mod tests {
365385
Ok(FileOrUrl {
366386
url: Some(_),
367387
file: None,
368-
version: None
388+
version: None,
389+
at_block: None,
369390
})
370391
));
371392
}

macro/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ fn fetch_metadata(args: &RuntimeMetadataArgs) -> Result<subxt_codegen::Metadata,
263263
false => MetadataVersion::Latest,
264264
};
265265

266-
from_url_blocking(url, version)
266+
from_url_blocking(url, version, None)
267267
.map_err(|e| CodegenError::Other(e.to_string()))
268268
.and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into))
269269
.map_err(|e| e.into_compile_error())?

utils/fetch-metadata/src/url.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! Fetch metadata from a URL.
66
77
use crate::Error;
8-
use codec::{Decode, Encode};
8+
use codec::{Decode, Encode};
99
use jsonrpsee::{
1010
core::client::ClientT, http_client::HttpClientBuilder, rpc_params, ws_client::WsClientBuilder,
1111
};
@@ -44,19 +44,19 @@ impl std::str::FromStr for MetadataVersion {
4444
}
4545

4646
/// Returns the metadata bytes from the provided URL.
47-
pub async fn from_url(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
47+
pub async fn from_url(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result<Vec<u8>, Error> {
4848
let bytes = match url.scheme() {
49-
"http" | "https" => fetch_metadata_http(url, version).await,
50-
"ws" | "wss" => fetch_metadata_ws(url, version).await,
49+
"http" | "https" => fetch_metadata_http(url, version, at_block_hash).await,
50+
"ws" | "wss" => fetch_metadata_ws(url, version, at_block_hash).await,
5151
invalid_scheme => Err(Error::InvalidScheme(invalid_scheme.to_owned())),
5252
}?;
5353

5454
Ok(bytes)
5555
}
5656

5757
/// Returns the metadata bytes from the provided URL, blocking the current thread.
58-
pub fn from_url_blocking(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
59-
tokio_block_on(from_url(url, version))
58+
pub fn from_url_blocking(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result<Vec<u8>, Error> {
59+
tokio_block_on(from_url(url, version, at_block_hash))
6060
}
6161

6262
// Block on some tokio runtime for sync contexts
@@ -68,34 +68,40 @@ fn tokio_block_on<T, Fut: std::future::Future<Output = T>>(fut: Fut) -> T {
6868
.block_on(fut)
6969
}
7070

71-
async fn fetch_metadata_ws(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
71+
async fn fetch_metadata_ws(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result<Vec<u8>, Error> {
7272
let client = WsClientBuilder::default()
7373
.request_timeout(std::time::Duration::from_secs(180))
7474
.max_buffer_capacity_per_subscription(4096)
7575
.build(url)
7676
.await?;
7777

78-
fetch_metadata(client, version).await
78+
fetch_metadata(client, version, at_block_hash).await
7979
}
8080

81-
async fn fetch_metadata_http(url: Url, version: MetadataVersion) -> Result<Vec<u8>, Error> {
81+
async fn fetch_metadata_http(url: Url, version: MetadataVersion, at_block_hash: Option<&str>) -> Result<Vec<u8>, Error> {
8282
let client = HttpClientBuilder::default()
8383
.request_timeout(std::time::Duration::from_secs(180))
8484
.build(url)?;
8585

86-
fetch_metadata(client, version).await
86+
fetch_metadata(client, version, at_block_hash).await
8787
}
8888

8989
/// The innermost call to fetch metadata:
90-
async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Result<Vec<u8>, Error> {
90+
async fn fetch_metadata(client: impl ClientT, version: MetadataVersion, at_block_hash: Option<&str>) -> Result<Vec<u8>, Error> {
9191
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
9292

93+
// Ensure always 0x prefix.
94+
let at_block_hash = at_block_hash
95+
.map(|hash| format!("0x{}", hash.strip_prefix("0x").unwrap_or(hash)));
96+
let at_block_hash = at_block_hash.as_deref();
97+
9398
// Fetch available metadata versions. If error, revert to legacy metadata code.
9499
async fn fetch_available_versions(
95100
client: &impl ClientT,
101+
at_block_hash: Option<&str>,
96102
) -> Result<Vec<u32>, Error> {
97103
let res: String = client
98-
.request("state_call", rpc_params!["Metadata_metadata_versions", "0x"])
104+
.request("state_call", rpc_params!["Metadata_metadata_versions", "0x", at_block_hash])
99105
.await?;
100106
let raw_bytes = hex::decode(res.trim_start_matches("0x"))?;
101107
Decode::decode(&mut &raw_bytes[..]).map_err(Into::into)
@@ -106,6 +112,7 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul
106112
client: &impl ClientT,
107113
version: MetadataVersion,
108114
supported_versions: Vec<u32>,
115+
at_block_hash: Option<&str>,
109116
) -> Result<Vec<u8>, Error> {
110117
// Return the version the user wants if it's supported:
111118
let version = match version {
@@ -141,7 +148,7 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul
141148
let metadata_string: String = client
142149
.request(
143150
"state_call",
144-
rpc_params!["Metadata_metadata_at_version", &version],
151+
rpc_params!["Metadata_metadata_at_version", &version, at_block_hash],
145152
)
146153
.await?;
147154
// Decode the metadata.
@@ -159,10 +166,11 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul
159166
// Fetch metadata using the "old" state_call interface
160167
async fn fetch_inner_legacy(
161168
client: &impl ClientT,
169+
at_block_hash: Option<&str>,
162170
) -> Result<Vec<u8>, Error> {
163171
// Fetch the metadata.
164172
let metadata_string: String = client
165-
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
173+
.request("state_call", rpc_params!["Metadata_metadata", "0x", at_block_hash])
166174
.await?;
167175

168176
// Decode the metadata.
@@ -171,16 +179,16 @@ async fn fetch_metadata(client: impl ClientT, version: MetadataVersion) -> Resul
171179
Ok(metadata.0)
172180
}
173181

174-
match fetch_available_versions(&client).await {
182+
match fetch_available_versions(&client, at_block_hash).await {
175183
Ok(supported_versions) => {
176-
fetch_inner(&client, version, supported_versions).await
184+
fetch_inner(&client, version, supported_versions, at_block_hash).await
177185
},
178186
Err(e) => {
179187
// The "new" interface failed. if the user is asking for V14 or the "latest"
180188
// metadata then try the legacy interface instead. Else, just return the
181189
// reason for failure.
182190
if matches!(version, MetadataVersion::Version(14) | MetadataVersion::Latest) {
183-
fetch_inner_legacy(&client).await
191+
fetch_inner_legacy(&client, at_block_hash).await
184192
} else {
185193
Err(e)
186194
}

0 commit comments

Comments
 (0)