Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metadata V15: Generate Runtime APIs #918

Merged
merged 78 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
8af6a40
Update frame-metadata to v15.1.0
lexnv Apr 10, 2023
dd89dd6
Enable V15 unstable metadata in frame-metadata
lexnv Apr 10, 2023
073aea9
metadata: Move validation hashing to dedicated file
lexnv Apr 10, 2023
3c2d78a
Use sp-metadata-ir from substrate to work with metadata
lexnv Apr 11, 2023
d2d29dc
Revert using sp-metadata-ir in favor of conversion to v15
lexnv Apr 13, 2023
a795b62
metadata: Convert v14 to v15
lexnv Apr 13, 2023
98360c3
metadata: Use v15 for validation
lexnv Apr 13, 2023
04dc695
codegen: Use v15 for codegen
lexnv Apr 13, 2023
47d84bc
metadata/bench: Use v15
lexnv Apr 13, 2023
365e78d
Adjust to v15 metadata
lexnv Apr 13, 2023
9302fa2
Adjust testing
lexnv Apr 13, 2023
dd48979
Merge remote-tracking branch 'origin/master' into lexnv/metadata_v15
lexnv Apr 13, 2023
bc2cf2a
Merge remote-tracking branch 'origin/master' into lexnv/metadata_v15
lexnv Apr 14, 2023
f52b92b
Merge remote-tracking branch 'origin/master' into lexnv/metadata_v15
lexnv Apr 17, 2023
9da2bf3
Merge remote-tracking branch 'origin/master' into lexnv/metadata_v15
lexnv Apr 18, 2023
7f3931a
Improve documentation
lexnv Apr 18, 2023
4663300
force CI
lexnv Apr 18, 2023
334f7fb
rpc: Fetch metadata at version
lexnv Apr 17, 2023
15ddaaa
artifacts: Update polkadot.scale from commit 6dc9e84dde2
lexnv Apr 17, 2023
78fafe6
codegen: Fetch V15 using the new API
lexnv Apr 18, 2023
561c16d
codegen: Add runtime API interface
lexnv Apr 18, 2023
d7141ec
metadata: Hash runtime API metadata for validation
lexnv Apr 18, 2023
d7652e5
metadata: Extract runtime API metadata wrapper from subxt::Metadata
lexnv Apr 18, 2023
21741d8
subxt: Adjust hashing cache to reflect root+item keys
lexnv Apr 18, 2023
bff5b7c
rpc: Add raw state_call API method
lexnv Apr 18, 2023
2546520
runtime_api: Add payload with static and dynamic variants
lexnv Apr 18, 2023
c1c8fd4
subxt: Allow payloads to call into the runtime
lexnv Apr 18, 2023
0f8a86d
examples: Add example to make a runtime API call both static and dynamic
lexnv Apr 18, 2023
d1229e7
Update polkadot.rs
lexnv Apr 18, 2023
16474c7
Merge remote-tracking branch 'origin/master' into lexnv/add_runtime_api
lexnv Apr 20, 2023
67d63b3
codegen: Simplify client fetching
lexnv Apr 20, 2023
82548ce
Address feedback and fallback to old API if needed
lexnv Apr 20, 2023
9f7d26e
runtime_api: Make mutability conditional on input params
lexnv Apr 20, 2023
1b60e89
Regenerate polkadot.rs
lexnv Apr 21, 2023
ae35fa7
metadata: Retain only pallets without runtime API info
lexnv Apr 21, 2023
df8e3b2
codegen: Retry via `Metadata_metadata` without conversion
lexnv Apr 21, 2023
ce2d6d6
payload: Remove `Decode` and change validation fn
lexnv Apr 21, 2023
8cfdbe3
Merge branch 'master' into lexnv/add_runtime_api
lexnv Apr 25, 2023
26688d3
metadata: Retain runtime API types
lexnv Apr 25, 2023
57790d6
codegen: Runtime APIs documentation based on flag
lexnv Apr 25, 2023
a58fbdc
Update examples/examples/custom_metadata_url.rs
lexnv Apr 25, 2023
fc5a952
Merge remote-tracking branch 'origin/lexnv/add_runtime_api' into lexn…
lexnv Apr 25, 2023
e355c14
Update artifacts from polkadot-a6cfdb16e9
lexnv Apr 25, 2023
320f260
Update polkadot.rs with polkadot-a6cfdb16e9
lexnv Apr 25, 2023
5754c09
codegen: Generate input structures for runtime API
lexnv Apr 25, 2023
719a004
runtime_api: Remove the static paylaod and use single impl
lexnv Apr 25, 2023
c8b3fee
examples: Fetch account nonce
lexnv Apr 25, 2023
cd9d1e6
testing: Adjust build script to fetch latest metadata
lexnv Apr 25, 2023
df24528
testing: Check account nonce from runtime API
lexnv Apr 25, 2023
ff6efca
Update cargo.lock
lexnv Apr 25, 2023
7bd91ff
codegen: Fix doc generation for runtime types
lexnv Apr 26, 2023
b597b5d
Merge remote-tracking branch 'origin/master' into lexnv/add_runtime_api
lexnv Apr 26, 2023
ee9e679
codegen: Rename `inputs` runtime calls module to `types`
lexnv Apr 27, 2023
834e3ad
codegen: Generate Calls structs inside the types module
lexnv Apr 27, 2023
3866c6b
testing: Check Alice account nonce before submitting the tx
lexnv Apr 27, 2023
69f5322
cli: Add metadata version option flag supporting v14 and unstable
lexnv Apr 27, 2023
014b758
cli: Specify version to fetch
lexnv Apr 27, 2023
334ee4c
subxt: Fallback to fetching latest stable metadata
lexnv Apr 28, 2023
dc3433c
subxt: Add unstable-metadata feature to fetch the latest
lexnv Apr 28, 2023
f74cf75
Merge branch 'master' into lexnv/add_runtime_api
lexnv May 2, 2023
2d8e68d
RuntimeVersion with Latest and Version(u32)
lexnv May 2, 2023
4273fb7
Update polkadot.rs
lexnv May 2, 2023
ff64caa
codegen: Adjust fetch_metadata to inspect version list
lexnv May 2, 2023
b6f5f7b
Merge remote-tracking branch 'origin/master' into lexnv/add_runtime_api
lexnv May 2, 2023
9e02da4
testing: Adjust metadata to metadata_legacy
lexnv May 2, 2023
0e17af5
events: Adjust docs to use metadata_legacy
lexnv May 3, 2023
b336ff3
have a pass over fetch_metadata
jsdw May 3, 2023
4138b6a
cargo fmt
jsdw May 3, 2023
05ebbe0
Option<String> when fetch metadata via latest API
jsdw May 3, 2023
bd665d1
clippy
jsdw May 3, 2023
9c92ade
fmt
jsdw May 3, 2023
f16525d
cli: Use the MetadataVersion from codegen
lexnv May 3, 2023
e632b66
Merge remote-tracking branch 'origin/lexnv/add_runtime_api' into lexn…
lexnv May 3, 2023
9bf0f66
cli: Specify latest as default for MetadataVersion
lexnv May 3, 2023
0655df8
Merge remote-tracking branch 'origin/master' into lexnv/add_runtime_api
lexnv May 3, 2023
f29c048
cli: Remove version from metadata and use the one from file_or_url
lexnv May 3, 2023
a032374
Fix clippy
lexnv May 3, 2023
0ea32fc
codegen: Decode metadata independently for different RPC calls
lexnv May 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,12 @@ impl std::str::FromStr for MetadataVersion {
type Err = String;

fn from_str(input: &str) -> Result<Self, Self::Err> {
const SUPPORTED_VERSIONS: &[u32] = &[14];

match input {
"unstable" => Ok(MetadataVersion { version: u32::MAX }),
version => {
let num: u32 = version
.parse()
.map_err(|_| format!("Invalid metadata version specified {:?}. Subxt supports the following versions {:?}", version, SUPPORTED_VERSIONS))?;

if !SUPPORTED_VERSIONS.iter().any(|&value| value == num) {
return Err(format!("Invalid metadata version specified {:?}. Subxt supports the following versions {:?}", version, SUPPORTED_VERSIONS));
}
.map_err(|_| format!("Invalid metadata version specified {:?}", version))?;

Ok(MetadataVersion { version: num })
}
Expand All @@ -105,9 +99,9 @@ impl std::str::FromStr for MetadataVersion {
impl From<MetadataVersion> for CodegenMetadataVersion {
fn from(input: MetadataVersion) -> CodegenMetadataVersion {
match input.version {
14 => CodegenMetadataVersion::V14,
u32::MAX => CodegenMetadataVersion::Unstable,
_ => panic!("MetadataVersion and CodegenMetadataVersion are not in sync!"),
14 => CodegenMetadataVersion::Latest,
Copy link
Collaborator

@jsdw jsdw May 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe just manually have an into_codegen_metadata_version(metadata_version: Option<MetadataVersion>) -> CodegenMetadataVersion so we don't have to hardcode the 14 here (I'm trying to get away from assuming anything about what the node has in terms of metadata versions :))?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or clap has default_value = <str> which we could use so that MetadataVersion could have a Latest variant and clap will default to using that. (maybe we can just use CodegenMetadataVersion directly in that case?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense! Have ended up using just the CodegenMetadataVersion here :D Thanks!

v => CodegenMetadataVersion::Version(v),
}
}
}
2 changes: 1 addition & 1 deletion codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub fn generate_runtime_api_from_url(
// Fetch latest unstable version, if that fails fall back to the latest stable.
let bytes = match fetch_metadata_bytes_blocking(url, MetadataVersion::Unstable) {
Ok(bytes) => bytes,
Err(_) => fetch_metadata_bytes_blocking(url, MetadataVersion::V14)?,
Err(_) => fetch_metadata_bytes_blocking(url, MetadataVersion::Latest)?,
};

generate_runtime_api_from_bytes(
Expand Down
120 changes: 98 additions & 22 deletions codegen/src/utils/fetch_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ use std::time::Duration;
/// The metadata version that is fetched from the node.
#[derive(Default)]
pub enum MetadataVersion {
/// Version 14.
/// Latest stable version of the metadata.
#[default]
V14,
Latest,
/// Fetch a specified version of the metadata.
Version(u32),
/// Latest unstable version of the metadata.
Unstable,
}
Expand Down Expand Up @@ -75,48 +77,122 @@ pub async fn fetch_metadata_hex(
Ok(hex_data)
}

async fn fetch_latest_stable(client: impl ClientT) -> Result<Vec<u8>, FetchMetadataError> {
// Fetch latest stable metadata of a node via `Metadata_metadata`.
let raw: String = client
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
.await?;
let raw_bytes = hex::decode(raw.trim_start_matches("0x"))?;
let bytes: frame_metadata::OpaqueMetadata = Decode::decode(&mut &raw_bytes[..])?;
Ok(bytes.0)
}

/// Execute runtime API call and return the specified runtime metadata version.
async fn fetch_metadata(
client: impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
const LATEST_STABLE_VERSION: u32 = 14;

// Note: `Metadata_metadata_versions` may not be present on all nodes.
// Once every node supports the new RPC methods, we can simplify the code a bit.
let supported: Option<Vec<u32>> = client
.request(
"state_call",
rpc_params!["Metadata_metadata_versions", "0x"],
)
.await
.ok()
.map(|raw: String| {
let raw_bytes = hex::decode(raw.trim_start_matches("0x"))?;
let versions: Vec<u32> = Decode::decode(&mut &raw_bytes[..])?;
Ok::<Vec<u32>, FetchMetadataError>(versions)
})
.transpose()?;
Copy link
Collaborator

@jsdw jsdw May 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the ? here lead to us exiting early if this call fails due to "Metadata_metadata_versions" not existing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here seems a bit complex after another look, I'll take a note to go back and do a plain-old match to make it easier to follow:

  • we initially do a .ok() which throws away the Result<String, Error> from the client and wraps into a Option<String>
  • we transpose an Option<Result<..>>, out of which the Result can only be part of the closure: hex decoding or coded::decod failed

I believe we should be safe here, I'll be a bit more explicit with a simple match and such :D


// Ensure the user requested a valid version if done implicitly.
if let (Some(supported_versions), MetadataVersion::Version(request_version)) =
(&supported, &version)
{
if !supported_versions.is_empty()
&& !supported_versions
.iter()
.any(|value| value == request_version)
{
return Err(FetchMetadataError::Other(format!(
"Metadata version {} not supported",
request_version
)))?;
}
}

let mut is_latest = false;
let version_num = match version {
MetadataVersion::V14 => 14,
MetadataVersion::Latest => {
// If he have a valid supported version, find the latest stable version number.
let version_num = if let Some(supported_versions) = &supported {
supported_versions
.iter()
.fold(None, |max, value| match (max, value) {
(None, value) => Some(value),
(Some(old_max), value) => {
if value != &UNSTABLE_METADATA_VERSION && value > old_max {
Some(value)
} else {
Some(old_max)
}
}
})
} else {
// List not exposed by node.
None
};

if let Some(version_num) = version_num {
// Use the latest stable from the provided list.
is_latest = true;
*version_num
} else {
// List is empty or the node does not expose the list of supported versions.
return fetch_latest_stable(client).await;
}
}
MetadataVersion::Version(version) => version,
MetadataVersion::Unstable => UNSTABLE_METADATA_VERSION,
};

// Fetch the latest stable version with `Metadata_metadata` API.
if version_num == LATEST_STABLE_VERSION {
let raw: String = client
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
.await?;
let raw_bytes = hex::decode(raw.trim_start_matches("0x"))?;
let bytes: frame_metadata::OpaqueMetadata = Decode::decode(&mut &raw_bytes[..])?;
return Ok(bytes.0);
}

// Other versions (including unstable) are fetched with `Metadata_metadata_at_version`.
let bytes = version_num.encode();
let version: String = format!("0x{}", hex::encode(&bytes));

let raw: String = client
let result: Result<String, _> = client
.request(
"state_call",
rpc_params!["Metadata_metadata_at_version", &version],
)
.await?;
.await;

let raw_bytes = hex::decode(raw.trim_start_matches("0x"))?;
match result {
Ok(raw) => {
let raw_bytes = hex::decode(raw.trim_start_matches("0x"))?;

let opaque: Option<frame_metadata::OpaqueMetadata> = Decode::decode(&mut &raw_bytes[..])?;
let bytes = opaque.ok_or(FetchMetadataError::Other(
"Metadata version not found".into(),
))?;
let opaque: Option<frame_metadata::OpaqueMetadata> =
Decode::decode(&mut &raw_bytes[..])?;
let bytes = opaque.ok_or(FetchMetadataError::Other(
"Metadata version not found".into(),
))?;

Ok(bytes.0)
Ok(bytes.0)
}
Err(err) => {
// Try to fetch the latest with `Metadata_metadata`.
if is_latest {
fetch_latest_stable(client).await
} else {
Err(err.into())
}
}
}
}

async fn fetch_metadata_ws(
Expand Down
6 changes: 3 additions & 3 deletions subxt/src/client/online_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ impl<T: Config> OnlineClient<T> {
const V15_METADATA_VERSION: u32 = u32::MAX;
match rpc.metadata_at_version(V15_METADATA_VERSION).await {
Ok(bytes) => Ok(bytes),
Err(_) => rpc.state_call_metadata().await,
Err(_) => rpc.metadata().await,
}
}

#[cfg(not(feature = "unstable-metadata"))]
rpc.state_call_metadata().await
rpc.metadata().await
}

/// Create an object which can be used to keep the runtime up to date
Expand Down Expand Up @@ -390,7 +390,7 @@ impl<T: Config> RuntimeUpdaterStream<T> {
Err(err) => return Some(Err(err)),
};

let metadata = match self.client.rpc().metadata(None).await {
let metadata = match self.client.rpc().metadata().await {
Ok(metadata) => metadata,
Err(err) => return Some(Err(err)),
};
Expand Down
2 changes: 1 addition & 1 deletion subxt/src/events/events_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl<T: Config> Events<T> {
/// .await?
/// .expect("didn't pass a block number; qed");
/// // Fetch the metadata of the given block.
/// let metadata = client.rpc().metadata(Some(block_hash)).await?;
/// let metadata = client.rpc().metadata_legacy(Some(block_hash)).await?;
/// // Fetch the events from the client.
/// let events = Events::new_from_client(metadata, block_hash, client);
/// # Ok(())
Expand Down
6 changes: 3 additions & 3 deletions subxt/src/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ impl<T: Config> Rpc<T> {
genesis_hash.ok_or_else(|| "Genesis hash not found".into())
}

/// Fetch the metadata
pub async fn metadata(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
/// Fetch the metadata via the legacy `state_getMetadata` RPC method.
pub async fn metadata_legacy(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
let bytes: types::Bytes = self
.client
.request("state_getMetadata", rpc_params![at])
Expand Down Expand Up @@ -400,7 +400,7 @@ impl<T: Config> Rpc<T> {
///
/// This returns the same output as [`Self::metadata`], but calls directly
/// into the runtime.
pub async fn state_call_metadata(&self) -> Result<Metadata, Error> {
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: frame_metadata::OpaqueMetadata =
self.state_call("Metadata_metadata", None, None).await?;

Expand Down
2 changes: 1 addition & 1 deletion testing/integration-tests/src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async fn runtime_api_call() -> Result<(), subxt::Error> {
};

// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await?;
let metadata = api.rpc().metadata_legacy(None).await?;
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion testing/integration-tests/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ async fn rpc_state_call() {
_ => panic!("Metadata V14 or V15 unavailable"),
};
// Compare the runtime API call against the `state_getMetadata`.
let metadata = api.rpc().metadata(None).await.unwrap();
let metadata = api.rpc().metadata_legacy(None).await.unwrap();
let metadata = metadata.runtime_metadata();
assert_eq!(&metadata_call, metadata);
}
Expand Down
Loading