Skip to content

Commit 1fd1eee

Browse files
lexnvjsdw
andauthored
Static Metadata Validation (paritytech#478)
* metadata: Implement MetadataHashable for deterministic hashing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Hash `scale_info::Field` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Hash `scale_info::Variant` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Hash `scale_info::TypeDef` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Hash pallet metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Avoid data representation collision via unique identifiers Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Finalize hashing on recursive types Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Cache recursive calls Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Move `MetadataHashable` to codegen to avoid cyclic dependency Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add pallet unique hash Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Wrap metadata as owned Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Use MetadataHashable wrapper for clients Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Generate runtime pallet uid from metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Validate metadata compatibility at the pallet level Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Modify examples and tests for the new API Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Implement metadata uid Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update polkadot with TryFrom implementation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * client: Change `to_runtime_api` to reflect TryFrom changes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * client: Skip full metadata validation option Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add option to skip pallet validation for TransactionApi Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add option to skip pallet validation for StorageApi Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update polkadot.rs with ability to skip pallet validation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Change `MetadataHashable` to per function implementation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Use metadata hashes functions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Use metadata hashes functions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Make `get_type_uid` private Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Rename metadata functions `*_uid` to `*_hash` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Update `get_field_hash` to use `codec::Encode` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Move metadata check from client to subxt::Metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Rename metadata check functions to follow `*_hash` naming Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Update polkadot.rs to reflect naming changes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Use `encode_to` for metadata generation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Update polkadot.rs to reflect `encode_to` changes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Specific name for visited set Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Provide cache to hashing functions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Compute metadata hash by sorted pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Get extrinsic hash Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Extend metadata hash with extrinsic and metadata type Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add cache as metadata parameter Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Update metadata hash to use cache Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Implement Default trait for MetadataHasherCache Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add cache for pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Move functionality to metadata crate Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Use subxt-metadata crate Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Remove metdata hashing functionality Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix vector capacity to include extrinisc and type hash Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add empty CLI Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Fetch metadata from substrate nodes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Log metadata hashes of provided nodes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Group compatible nodes by metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Simplify hash map insertion Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Move full metadata check to function Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-cli: Group metadata validation at the pallet level Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Persist metadata cache Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Move compatibility cli from subxt-metadata to subxt-cli Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Remove cli from subxt-metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * cli: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix compatible metadata when pallets are registered in different order Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Handle result of pallet hashing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Remove type cache for deterministic hashing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add test assets from `substrate-node-template` tag `polkadot-v0.9.17` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-tests: Check cache hashing for Balances pallet Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix `get_type_hash` clippy issue Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-tests: Compare one time cache with persistent cache Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-test: Check metadata hash populates cache for pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-tests: Simplify `cache_deterministic_hash` test Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata-tests: Check deterministic metadata for different order pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Implement TransactionApiUnchecked for skipping pallet validation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Implement StorageApiUnchecked for skipping pallet validation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Remove skip_pallet_validation boolean Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Implement ClientUnchecked for skipping metadata validation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update examples of rpc_call to skip metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Remove heck dependency Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add pallet name as an identifier for pallet hashing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Implement MetadataHashDetails Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Adjust testing to `MetadataHashDetails` interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Remove extra `pallet_name` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Fix clippy issue Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Change StorageApi to support `_unchecked` methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Change TransactionApi to support `_unchecked` methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Switch back from `TryFrom` to `From` for `subxt::Client` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen, subxt: Remove `ClientUnchecked` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Expose `validate_metadata` as validation of compatibility method Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Update to the new interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Update test integration to latest interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Check different pallet index order Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Check recursive type hashing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Check recursive types registered in different order Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Fix recursive types warning Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Remove test assets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Extend tests to verify cached pallet values Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Add metadata compatiblity example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Revert balance_transfer to initial form Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add ConstantsApi metadata check Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Modify tests to accomodate ConstantsApi changes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Modify verified version Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Generate polkadot.rs from `0.9.18-4542a603cc-aarch64-macos` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Update polkadot_metadata.scale from `0.9.18-4542a603cc-aarch64-macos` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Update documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Modify default pallet usage Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Remove hex dependency Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Add MetadataTestType to capture complex types Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Update tests to use complex types Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Check metadata correctness via extending pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Extend pallet hash with Events Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Extend pallet hash with constants Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Extend pallet hash with error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Extend metadata compatibiliy with StorageApi and ConstantsApi Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Modify comments and documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Benchmarks for full validation and pallet validation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/benches: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Hash metadata just by inspecting the provided pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Make pallets generic over T for `AsRef<str>` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Expose the name of the pallets composing the metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Update polkadot.rs with pallets name Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Obtain metadata hash only by inspecting pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen,subxt: Extend the metadata hash to utilize just pallets Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Update polkadot.rs with client metadata has per pallet Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Test `get_metadata_per_pallet_hash` correctness Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/benches: Fix decode of metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * [static metadata] validate storage, calls and constants per call (paritytech#507) * validate storage, calls and constants per call * fix typo * cache per-thing hashing, move an Arc, remove some unused bits * create hash_cache to simplify metadata call/constant/storage caching * simplify/robustify the caching logic to help prevent mismatch between get and set * cargo fmt * Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * bench the per-call metadata functions * metadata: Add test for `node_template_runtime_variant` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * ensure criteron cli opts work * group benchmarks and avoid unwrap issues * metadata: Check template runtime for handling the pallet swap order case Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Remove debug logs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Optimise by removing field's name and type_name and type's path Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Refactor `get_type_hash` to break recursion earlier Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add tests for `hash_cache` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add tests for checking Metadata Inner cache Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Check semantic changes inside enum and struct fields Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Add enums named differently with compatible semantic meaning Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Guard testing of release versions for `node_template_runtime` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Improve documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update polkadot.rs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata/tests: Manually construct type of `node_template_runtimeL::Call` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * no more special Call handling, avoid a little cloning, and actually sort by name * remove unused deps and fmt * RuntimeMetadataLastVersion => RuntimeMetadataV14 * remove a bunch of allocations in the metadata hashing, speed up from ~17ms to ~5ms * update release docs to release metadata crate too * subxt: Remove codegen dependency Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Replace std RwLock with parking_lot Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Add ws address to `TestNodeProcess` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Add metadata validation integration test Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Allow setting metadata on the ClientBuilder Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Check incompatible metadatas Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Fix constant hashing for deterministic output Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Check metadata validation for constants Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Test validation for calls Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt/tests: Test validation for storage Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Expose `set_metadata` for testing only Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Guard metadata tests under integration-tests Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: James Wilson <james@jsdw.me>
1 parent 5605fbd commit 1fd1eee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+17843
-4707
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"codegen",
66
"examples",
77
"macro",
8+
"metadata",
89
"subxt",
910
"test-runtime"
1011
]

RELEASING.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ We also assume that ongoing work done is being merged directly to the `master` b
6767
a little time in between each to let crates.io catch up with what we've published).
6868
6969
```
70-
(cd codegen && cargo publish) && \
70+
(cd metadata && cargo publish) && \
71+
(cd codegen && cargo publish) && \
7172
sleep 10 && \
7273
(cd macro && cargo publish) && \
7374
sleep 10 && \

cli/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ path = "src/main.rs"
1717
[dependencies]
1818
# perform subxt codegen
1919
subxt-codegen = { version = "0.20.0", path = "../codegen" }
20+
# perform node compatibility
21+
subxt-metadata = { version = "0.20.0", path = "../metadata" }
22+
# information of portable registry
23+
scale-info = "2.0.0"
2024
# parse command line args
2125
structopt = "0.3.25"
2226
# make the request to a substrate node to get the metadata

cli/src/main.rs

+126-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,22 @@ use color_eyre::eyre::{
1818
self,
1919
WrapErr,
2020
};
21-
use frame_metadata::RuntimeMetadataPrefixed;
21+
use frame_metadata::{
22+
RuntimeMetadata,
23+
RuntimeMetadataPrefixed,
24+
RuntimeMetadataV14,
25+
META_RESERVED,
26+
};
2227
use scale::{
2328
Decode,
2429
Input,
2530
};
31+
use serde::{
32+
Deserialize,
33+
Serialize,
34+
};
2635
use std::{
36+
collections::HashMap,
2737
fs,
2838
io::{
2939
self,
@@ -34,6 +44,10 @@ use std::{
3444
};
3545
use structopt::StructOpt;
3646
use subxt_codegen::GeneratedTypeDerives;
47+
use subxt_metadata::{
48+
get_metadata_hash,
49+
get_pallet_hash,
50+
};
3751

3852
/// Utilities for working with substrate metadata for subxt.
3953
#[derive(Debug, StructOpt)]
@@ -75,6 +89,18 @@ enum Command {
7589
#[structopt(long = "derive")]
7690
derives: Vec<String>,
7791
},
92+
/// Verify metadata compatibility between substrate nodes.
93+
Compatibility {
94+
/// Urls of the substrate nodes to verify for metadata compatibility.
95+
#[structopt(name = "nodes", long, use_delimiter = true, parse(try_from_str))]
96+
nodes: Vec<url::Url>,
97+
/// Check the compatibility of metadata for a particular pallet.
98+
///
99+
/// ### Note
100+
/// The validation will omit the full metadata check and focus instead on the pallet.
101+
#[structopt(long, parse(try_from_str))]
102+
pallet: Option<String>,
103+
},
78104
}
79105

80106
fn main() -> color_eyre::Result<()> {
@@ -126,6 +152,105 @@ fn main() -> color_eyre::Result<()> {
126152
codegen(&mut &bytes[..], derives)?;
127153
Ok(())
128154
}
155+
Command::Compatibility { nodes, pallet } => {
156+
match pallet {
157+
Some(pallet) => handle_pallet_metadata(nodes.as_slice(), pallet.as_str()),
158+
None => handle_full_metadata(nodes.as_slice()),
159+
}
160+
}
161+
}
162+
}
163+
164+
fn handle_pallet_metadata(nodes: &[url::Url], name: &str) -> color_eyre::Result<()> {
165+
#[derive(Serialize, Deserialize, Default)]
166+
#[serde(rename_all = "camelCase")]
167+
struct CompatibilityPallet {
168+
pallet_present: HashMap<String, Vec<String>>,
169+
pallet_not_found: Vec<String>,
170+
}
171+
172+
let mut compatibility: CompatibilityPallet = Default::default();
173+
for node in nodes.iter() {
174+
let metadata = fetch_runtime_metadata(node)?;
175+
176+
match metadata.pallets.iter().find(|pallet| pallet.name == name) {
177+
Some(pallet_metadata) => {
178+
let hash = get_pallet_hash(&metadata.types, pallet_metadata);
179+
let hex_hash = hex::encode(hash);
180+
println!(
181+
"Node {:?} has pallet metadata hash {:?}",
182+
node.as_str(),
183+
hex_hash
184+
);
185+
186+
compatibility
187+
.pallet_present
188+
.entry(hex_hash)
189+
.or_insert_with(Vec::new)
190+
.push(node.as_str().to_string());
191+
}
192+
None => {
193+
compatibility
194+
.pallet_not_found
195+
.push(node.as_str().to_string());
196+
}
197+
}
198+
}
199+
200+
println!(
201+
"\nCompatible nodes by pallet\n{}",
202+
serde_json::to_string_pretty(&compatibility)
203+
.context("Failed to parse compatibility map")?
204+
);
205+
206+
Ok(())
207+
}
208+
209+
fn handle_full_metadata(nodes: &[url::Url]) -> color_eyre::Result<()> {
210+
let mut compatibility_map: HashMap<String, Vec<String>> = HashMap::new();
211+
for node in nodes.iter() {
212+
let metadata = fetch_runtime_metadata(node)?;
213+
let hash = get_metadata_hash(&metadata);
214+
let hex_hash = hex::encode(hash);
215+
println!("Node {:?} has metadata hash {:?}", node.as_str(), hex_hash,);
216+
217+
compatibility_map
218+
.entry(hex_hash)
219+
.or_insert_with(Vec::new)
220+
.push(node.as_str().to_string());
221+
}
222+
223+
println!(
224+
"\nCompatible nodes\n{}",
225+
serde_json::to_string_pretty(&compatibility_map)
226+
.context("Failed to parse compatibility map")?
227+
);
228+
229+
Ok(())
230+
}
231+
232+
fn fetch_runtime_metadata(url: &url::Url) -> color_eyre::Result<RuntimeMetadataV14> {
233+
let (_, bytes) = fetch_metadata(url)?;
234+
235+
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
236+
if metadata.0 != META_RESERVED {
237+
return Err(eyre::eyre!(
238+
"Node {:?} has invalid metadata prefix: {:?} expected prefix: {:?}",
239+
url.as_str(),
240+
metadata.0,
241+
META_RESERVED
242+
))
243+
}
244+
245+
match metadata.1 {
246+
RuntimeMetadata::V14(v14) => Ok(v14),
247+
_ => {
248+
Err(eyre::eyre!(
249+
"Node {:?} with unsupported metadata version: {:?}",
250+
url.as_str(),
251+
metadata.1
252+
))
253+
}
129254
}
130255
}
131256

codegen/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ proc-macro-error = "1.0.4"
2222
quote = "1.0.8"
2323
syn = "1.0.58"
2424
scale-info = { version = "2.0.0", features = ["bit-vec"] }
25+
sp-core = { version = "6.0.0" }
26+
subxt-metadata = { version = "0.20.0", path = "../metadata" }
2527

2628
[dev-dependencies]
2729
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }

codegen/src/api/calls.rs

+17-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::types::{
1919
TypeGenerator,
2020
};
2121
use frame_metadata::{
22+
v14::RuntimeMetadataV14,
2223
PalletCallMetadata,
2324
PalletMetadata,
2425
};
@@ -35,6 +36,7 @@ use quote::{
3536
use scale_info::form::PortableForm;
3637

3738
pub fn generate_calls(
39+
metadata: &RuntimeMetadataV14,
3840
type_gen: &TypeGenerator,
3941
pallet: &PalletMetadata<PortableForm>,
4042
call: &PalletCallMetadata<PortableForm>,
@@ -48,7 +50,7 @@ pub fn generate_calls(
4850
);
4951
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
5052
.iter_mut()
51-
.map(|struct_def| {
53+
.map(|(variant_name, struct_def)| {
5254
let (call_fn_args, call_args): (Vec<_>, Vec<_>) =
5355
match struct_def.fields {
5456
CompositeDefFields::Named(ref named_fields) => {
@@ -74,30 +76,36 @@ pub fn generate_calls(
7476
};
7577

7678
let pallet_name = &pallet.name;
77-
let call_struct_name = &struct_def.name;
78-
let function_name = struct_def.name.to_string().to_snake_case();
79-
let fn_name = format_ident!("{}", function_name);
79+
let call_name = &variant_name;
80+
let struct_name = &struct_def.name;
81+
let call_hash = subxt_metadata::get_call_hash(metadata, pallet_name, call_name)
82+
.unwrap_or_else(|_| abort_call_site!("Metadata information for the call {}_{} could not be found", pallet_name, call_name));
8083

84+
let fn_name = format_ident!("{}", variant_name.to_snake_case());
8185
// Propagate the documentation just to `TransactionApi` methods, while
8286
// draining the documentation of inner call structures.
8387
let docs = struct_def.docs.take();
8488
// The call structure's documentation was stripped above.
8589
let call_struct = quote! {
8690
#struct_def
8791

88-
impl ::subxt::Call for #call_struct_name {
92+
impl ::subxt::Call for #struct_name {
8993
const PALLET: &'static str = #pallet_name;
90-
const FUNCTION: &'static str = #function_name;
94+
const FUNCTION: &'static str = #call_name;
9195
}
9296
};
9397
let client_fn = quote! {
9498
#docs
9599
pub fn #fn_name(
96100
&self,
97101
#( #call_fn_args, )*
98-
) -> ::subxt::SubmittableExtrinsic<'a, T, X, #call_struct_name, DispatchError, root_mod::Event> {
99-
let call = #call_struct_name { #( #call_args, )* };
100-
::subxt::SubmittableExtrinsic::new(self.client, call)
102+
) -> Result<::subxt::SubmittableExtrinsic<'a, T, X, #struct_name, DispatchError, root_mod::Event>, ::subxt::BasicError> {
103+
if self.client.metadata().call_hash::<#struct_name>()? == [#(#call_hash,)*] {
104+
let call = #struct_name { #( #call_args, )* };
105+
Ok(::subxt::SubmittableExtrinsic::new(self.client, call))
106+
} else {
107+
Err(::subxt::MetadataError::IncompatibleMetadata.into())
108+
}
101109
}
102110
};
103111
(call_struct, client_fn)

codegen/src/api/constants.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@
1616

1717
use crate::types::TypeGenerator;
1818
use frame_metadata::{
19+
v14::RuntimeMetadataV14,
1920
PalletConstantMetadata,
2021
PalletMetadata,
2122
};
2223
use heck::ToSnakeCase as _;
2324
use proc_macro2::TokenStream as TokenStream2;
25+
use proc_macro_error::abort_call_site;
2426
use quote::{
2527
format_ident,
2628
quote,
2729
};
2830
use scale_info::form::PortableForm;
2931

3032
pub fn generate_constants(
33+
metadata: &RuntimeMetadataV14,
3134
type_gen: &TypeGenerator,
3235
pallet: &PalletMetadata<PortableForm>,
3336
constants: &[PalletConstantMetadata<PortableForm>],
@@ -37,16 +40,23 @@ pub fn generate_constants(
3740
let fn_name = format_ident!("{}", constant.name.to_snake_case());
3841
let pallet_name = &pallet.name;
3942
let constant_name = &constant.name;
43+
let constant_hash = subxt_metadata::get_constant_hash(metadata, pallet_name, constant_name)
44+
.unwrap_or_else(|_| abort_call_site!("Metadata information for the constant {}_{} could not be found", pallet_name, constant_name));
45+
4046
let return_ty = type_gen.resolve_type_path(constant.ty.id(), &[]);
4147
let docs = &constant.docs;
4248

4349
quote! {
4450
#( #[doc = #docs ] )*
4551
pub fn #fn_name(&self) -> ::core::result::Result<#return_ty, ::subxt::BasicError> {
46-
let pallet = self.client.metadata().pallet(#pallet_name)?;
47-
let constant = pallet.constant(#constant_name)?;
48-
let value = ::subxt::codec::Decode::decode(&mut &constant.value[..])?;
49-
Ok(value)
52+
if self.client.metadata().constant_hash(#pallet_name, #constant_name)? == [#(#constant_hash,)*] {
53+
let pallet = self.client.metadata().pallet(#pallet_name)?;
54+
let constant = pallet.constant(#constant_name)?;
55+
let value = ::subxt::codec::Decode::decode(&mut &constant.value[..])?;
56+
Ok(value)
57+
} else {
58+
Err(::subxt::MetadataError::IncompatibleMetadata.into())
59+
}
5060
}
5161
}
5262
});

codegen/src/api/events.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ pub fn generate_events(
3535
|name| name.into(),
3636
"Event",
3737
);
38-
let event_structs = struct_defs.iter().map(|struct_def| {
38+
let event_structs = struct_defs.iter().map(|(variant_name, struct_def)| {
3939
let pallet_name = &pallet.name;
4040
let event_struct = &struct_def.name;
41-
let event_name = struct_def.name.to_string();
41+
let event_name = variant_name;
4242

4343
quote! {
4444
#struct_def

0 commit comments

Comments
 (0)