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

Add storage_version() and runtime_wasm_code() to storage #1111

Merged
merged 9 commits into from
Aug 10, 2023
40 changes: 39 additions & 1 deletion subxt/src/storage/storage_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
// see LICENSE for license details.

use super::storage_address::{StorageAddress, Yes};

use crate::{
client::OnlineClientT,
error::{Error, MetadataError},
metadata::{DecodeWithMetadata, Metadata},
rpc::types::{StorageData, StorageKey},
Config,
};
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
Expand Down Expand Up @@ -237,6 +239,42 @@ where
})
}
}

/// The storage version of a pallet.
/// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type.
pub async fn storage_version(&self, pallet_name: impl AsRef<str>) -> Result<u16, Error> {
// check that the pallet exists in the metadata:
self.client
.metadata()
.pallet_by_name(pallet_name.as_ref())
.ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.as_ref().into()))?;

// construct the storage key. This is done similarly in `frame_support::traits::metadata::StorageVersion::storage_key()`.
pub const STORAGE_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__STORAGE_VERSION__:";
let mut key_bytes: Vec<u8> = vec![];
key_bytes.extend(&sp_core_hashing::twox_128(pallet_name.as_ref().as_bytes()));
key_bytes.extend(&sp_core_hashing::twox_128(
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
));

// fetch the raw bytes and decode them into the StorageVersion struct:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Does this comment needs updating?

let storage_version_bytes = self.fetch_raw(&key_bytes).await?.ok_or_else(|| {
format!(
"Unexpected: entry for storage version in pallet \"{}\" not found",
pallet_name.as_ref()
)
})?;
u16::decode(&mut &storage_version_bytes[..]).map_err(Into::into)
}

/// Fetches the Wasm code of the runtime.
pub async fn runtime_wasm_code(&self) -> Result<Vec<u8>, Error> {
// note: this should match the `CODE` constant in `sp_core::storage::well_known_keys`
const CODE: &str = ":code";
self.fetch_raw(CODE.as_bytes()).await?.ok_or_else(|| {
format!("Unexpected: entry for well known key \"{CODE}\" not found").into()
})
}
}

/// Iterates over key value pairs in a map.
Expand Down Expand Up @@ -338,7 +376,7 @@ fn validate_storage(
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into())
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
Expand Down
2 changes: 1 addition & 1 deletion subxt/src/tx/tx_progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
.iter()
.position(|ext| {
use crate::config::Hasher;
let Ok((_,stripped)) = strip_compact_prefix(&ext.0) else {
let Ok((_, stripped)) = strip_compact_prefix(&ext.0) else {
return false;
};
let hash = T::Hasher::hash_of(&stripped);
Expand Down
4 changes: 2 additions & 2 deletions subxt/src/utils/wrapper_opaque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ impl<T> EncodeAsType for WrapperKeepOpaque<T> {
use scale_encode::error::{Error, ErrorKind, Kind};

let Some(ty) = types.resolve(type_id) else {
return Err(Error::new(ErrorKind::TypeNotFound(type_id)))
return Err(Error::new(ErrorKind::TypeNotFound(type_id)));
};

// Do a basic check that the target shape lines up.
let scale_info::TypeDef::Composite(_) = &ty.type_def else {
return Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected: type_id,
}))
}));
};

// Check that the name also lines up.
Expand Down
30 changes: 30 additions & 0 deletions testing/integration-tests/src/full_client/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,33 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> {
assert_eq!(entry.map(|a| a.amount), Some(123));
Ok(())
}

#[tokio::test]
async fn storage_runtime_wasm_code() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let wasm_blob = api.storage().at_latest().await?.runtime_wasm_code().await?;
assert!(wasm_blob.len() > 1000); // the wasm should be super big
Ok(())
}

#[tokio::test]
async fn storage_pallet_storage_version() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();

// cannot assume anything about version number, but should work to fetch it
let _version = api
.storage()
.at_latest()
.await?
.storage_version("System")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Crazy thought here; could we use this to do a quick version validation? Similar to our hashing, I guess that would require substrate to constantly modify these numbers appropriately with changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When would that version validation happen? I guess we would need to make about 30 concurrent calls to validate all pallets. Or we might just validate the pallet whenever a storage query/call is made for a specific pallet?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We already validate the keys and value types on a per-entry basis so probably what we have is already better (I imagine the version might increment for every storage change, but not really sure what it represents exactly?)

.await?;
let _version = api
.storage()
.at_latest()
.await?
.storage_version("Balances")
.await?;
Ok(())
}
Loading