-
Notifications
You must be signed in to change notification settings - Fork 261
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
Suggestions for storage value decoding #1457
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -211,12 +211,10 @@ pub enum StorageAddressError { | |
#[error("Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata.")] | ||
UnexpectedAddressBytes, | ||
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose. | ||
#[error("An invalid hasher was used to reconstruct a value of type {ty_name} (id={ty_id}) from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher")] | ||
#[error("An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the |
||
HasherCannotReconstructKey { | ||
/// Type id of the key's type. | ||
ty_id: u32, | ||
/// Type name of the key's type. | ||
ty_name: String, | ||
/// The invalid hasher that caused this error. | ||
hasher: StorageHasher, | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
use super::utils::strip_storage_hash_bytes; | ||
use crate::{ | ||
dynamic::DecodedValueThunk, | ||
error::{Error, StorageAddressError}, | ||
metadata::{DecodeWithMetadata, Metadata}, | ||
metadata::Metadata, | ||
utils::{Encoded, Static}, | ||
}; | ||
use scale_decode::{visitor::IgnoreVisitor, DecodeAsType}; | ||
use scale_encode::EncodeAsType; | ||
use scale_info::PortableRegistry; | ||
use scale_value::Value; | ||
use subxt_metadata::StorageHasher; | ||
|
||
use derivative::Derivative; | ||
|
@@ -21,25 +22,29 @@ pub trait StorageKey { | |
// But that plays poorly with the `Flatten` and `Chain` structs. | ||
fn keys_len(&self) -> usize; | ||
|
||
/// Attempts to decode the StorageKey from a storage address that has been stripped of its root bytes. | ||
/// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent. | ||
/// | ||
/// Example: Imagine The `StorageKey` is a tuple (A,B) and the hashers are: [Blake2_128Concat, Twox64Concat]. | ||
/// Example: Imagine The `StorageKey` is a tuple `(A,B)` and the hashers are `[Blake2_128Concat, Twox64Concat]`. | ||
/// Then the memory layout of the storage address is: | ||
/// | ||
/// ```txt | ||
/// | 8 bytes pallet hash | 8 bytes entry hash | 16 bytes hash of A | ... bytes of A | 8 bytes hash of B | ... bytes of B | | ||
/// | 16 byte hash of A | n bytes for SCALE encoded A | 8 byte hash of B | n bytes for SCALE encoded B | | ||
/// ``` | ||
/// `cursor` should point into a region after those first 16 bytes, at the start of a new hash. | ||
/// `hashers_and_ty_ids` should consume all the hashers that have been used for decoding, such that there are less hashers coming to the next key. | ||
/// | ||
/// Implementations of this must advance the `bytes` and `hashers_and_ty_ids` cursors to consume any that they are using, or | ||
/// return an error if they cannot appropriately do so. When a tuple of such implementations is given, each implementation | ||
/// in the tuple receives the remaining un-consumed bytes and hashers from the previous ones. | ||
fn decode_from_bytes( | ||
cursor: &mut &[u8], | ||
bytes: &mut &[u8], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. both this and the below are "cursors", so I renamed this one |
||
hashers_and_ty_ids: &mut &[(StorageHasher, u32)], | ||
metadata: &Metadata, | ||
) -> Result<Self, Error> | ||
where | ||
Self: Sized + 'static; | ||
} | ||
|
||
/// Implement `StorageKey` for `()` which can be used for keyless storage entries. | ||
/// Implement `StorageKey` for `()` which can be used for keyless storage entries, | ||
/// or to otherwise just ignore some entry. | ||
impl StorageKey for () { | ||
fn keys_iter(&self) -> impl Iterator<Item = &dyn EncodeAsType> { | ||
std::iter::empty() | ||
|
@@ -50,18 +55,20 @@ impl StorageKey for () { | |
} | ||
|
||
fn decode_from_bytes( | ||
_cursor: &mut &[u8], | ||
bytes: &mut &[u8], | ||
hashers_and_ty_ids: &mut &[(StorageHasher, u32)], | ||
_metadata: &Metadata, | ||
metadata: &Metadata, | ||
) -> Result<Self, Error> { | ||
if hashers_and_ty_ids.is_empty() { | ||
return Err(StorageAddressError::WrongNumberOfHashers { | ||
hashers: 0, | ||
fields: 1, | ||
} | ||
.into()); | ||
} | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance cursor by 1 | ||
// If no hashers, we just do nothing. | ||
let Some((hasher, ty_id)) = hashers_and_ty_ids.first() else { | ||
return Ok(()); | ||
}; | ||
|
||
// Consume the hash bytes (we don't care about the key output here). | ||
consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())?; | ||
// Advance our hasher cursor as well now that we've used it. | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[1..]; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
@@ -71,8 +78,8 @@ impl StorageKey for () { | |
#[derive(Derivative)] | ||
#[derivative(Clone(bound = ""), Debug(bound = ""))] | ||
pub struct StaticStorageKey<K: ?Sized> { | ||
pub(super) bytes: Static<Encoded>, | ||
pub(super) _marker: std::marker::PhantomData<K>, | ||
bytes: Static<Encoded>, | ||
_marker: std::marker::PhantomData<K>, | ||
} | ||
|
||
impl<K: codec::Encode + ?Sized> StaticStorageKey<K> { | ||
|
@@ -111,7 +118,7 @@ impl<K: ?Sized> StorageKey for StaticStorageKey<K> { | |
} | ||
|
||
fn decode_from_bytes( | ||
cursor: &mut &[u8], | ||
bytes: &mut &[u8], | ||
hashers_and_ty_ids: &mut &[(StorageHasher, u32)], | ||
metadata: &Metadata, | ||
) -> Result<Self, Error> | ||
|
@@ -125,53 +132,28 @@ impl<K: ?Sized> StorageKey for StaticStorageKey<K> { | |
} | ||
.into()); | ||
}; | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance cursor by 1 | ||
decode_storage_key_from_hash(cursor, hasher, *ty_id, metadata) | ||
} | ||
} | ||
|
||
pub fn decode_storage_key_from_hash<K: ?Sized>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this specific-to-StaticStorageKey function and made a slightly more general |
||
cursor: &mut &[u8], | ||
hasher: &StorageHasher, | ||
ty_id: u32, | ||
metadata: &Metadata, | ||
) -> Result<StaticStorageKey<K>, Error> { | ||
strip_storage_hash_bytes(cursor, hasher)?; | ||
|
||
let bytes = *cursor; | ||
if let Err(err) = scale_decode::visitor::decode_with_visitor( | ||
cursor, | ||
ty_id, | ||
metadata.types(), | ||
scale_decode::visitor::IgnoreVisitor, | ||
) { | ||
return Err(scale_decode::Error::from(err).into()); | ||
// Advance the bytes cursor, returning any key bytes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the |
||
let key_bytes = consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())?; | ||
// Advance the hasher cursor now we've used it. | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[1..]; | ||
|
||
// if the hasher had no key appended, we can't decode it into a `StaticStorageKey`. | ||
let Some(key_bytes) = key_bytes else { | ||
return Err(StorageAddressError::HasherCannotReconstructKey { | ||
ty_id: *ty_id, | ||
hasher: *hasher, | ||
} | ||
.into()); | ||
}; | ||
|
||
// Return the key bytes. | ||
let key = StaticStorageKey { | ||
bytes: Static(Encoded(key_bytes.to_vec())), | ||
_marker: std::marker::PhantomData::<K>, | ||
}; | ||
Ok(key) | ||
} | ||
let bytes_decoded = bytes.len() - cursor.len(); | ||
|
||
// Note: This validation check makes sure, only zero-sized types can be decoded from | ||
// hashers that do not support reconstruction of a value | ||
if !hasher.hash_contains_unhashed_key() && bytes_decoded > 0 { | ||
let ty_name = metadata | ||
.types() | ||
.resolve(ty_id) | ||
.expect("ty_id is in metadata, because decode_with_visitor did not fail above; qed") | ||
.path | ||
.to_string(); | ||
return Err(StorageAddressError::HasherCannotReconstructKey { | ||
ty_id, | ||
ty_name, | ||
hasher: *hasher, | ||
} | ||
.into()); | ||
}; | ||
|
||
let key_bytes = bytes[..bytes_decoded].to_vec(); | ||
let key = StaticStorageKey { | ||
bytes: Static(Encoded(key_bytes)), | ||
_marker: std::marker::PhantomData::<K>, | ||
}; | ||
Ok(key) | ||
} | ||
|
||
impl StorageKey for Vec<scale_value::Value> { | ||
|
@@ -186,32 +168,67 @@ impl StorageKey for Vec<scale_value::Value> { | |
} | ||
|
||
fn decode_from_bytes( | ||
cursor: &mut &[u8], | ||
bytes: &mut &[u8], | ||
hashers_and_ty_ids: &mut &[(StorageHasher, u32)], | ||
metadata: &Metadata, | ||
) -> Result<Self, Error> | ||
where | ||
Self: Sized + 'static, | ||
{ | ||
let mut hashers_and_ty_ids_iter = hashers_and_ty_ids.iter(); | ||
let mut result: Vec<scale_value::Value> = vec![]; | ||
let mut n = 0; | ||
while !cursor.is_empty() { | ||
let Some((hasher, ty_id)) = hashers_and_ty_ids_iter.next() else { | ||
// Still bytes left, but no hashers and type ids anymore to pull from: this is an unexpected error. | ||
return Err(StorageAddressError::UnexpectedAddressBytes.into()); | ||
}; | ||
strip_storage_hash_bytes(cursor, hasher)?; | ||
let decoded = DecodedValueThunk::decode_with_metadata(cursor, *ty_id, metadata)?; | ||
let value = decoded.to_value()?; | ||
result.push(value.remove_context()); | ||
n += 1; | ||
for (hasher, ty_id) in hashers_and_ty_ids.iter() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a look and I just decoded directly into a Value and figured it may as well go into a for loop where we check for remaining bytes at the end. |
||
match consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())? { | ||
Some(value_bytes) => { | ||
let value = | ||
Value::decode_as_type(&mut &*value_bytes, *ty_id, metadata.types())?; | ||
result.push(value.remove_context()); | ||
} | ||
None => { | ||
result.push(Value::unnamed_composite([])); | ||
} | ||
} | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance by 1 each time. | ||
} | ||
|
||
// We've consumed all of the hashers, so we expect to also consume all of the bytes: | ||
if !bytes.is_empty() { | ||
return Err(StorageAddressError::UnexpectedAddressBytes.into()); | ||
} | ||
*hashers_and_ty_ids = &hashers_and_ty_ids[n..]; // Advance cursor by n | ||
|
||
Ok(result) | ||
} | ||
} | ||
|
||
// Skip over the hash bytes (including any key at the end), returning bytes | ||
// representing the key if one exists, or None if the hasher has no key appended. | ||
fn consume_hash_returning_key_bytes<'a>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just captures a thing we want to do in a few places, which is to skip over any bytes a StorageHasher represents, and then if there are bytes for the key, then return them. Using this means you don't have to worry about consuming the right number of bytes anywhere else. |
||
bytes: &mut &'a [u8], | ||
hasher: StorageHasher, | ||
ty_id: u32, | ||
types: &PortableRegistry, | ||
) -> Result<Option<&'a [u8]>, Error> { | ||
// Strip the bytes off for the actual hash, consuming them. | ||
let bytes_to_strip = hasher.len_excluding_key(); | ||
if bytes.len() < bytes_to_strip { | ||
return Err(StorageAddressError::UnexpectedAddressBytes.into()); | ||
} | ||
*bytes = &bytes[bytes_to_strip..]; | ||
|
||
// Now, find the bytes representing the key, consuming them. | ||
let before_key = *bytes; | ||
if hasher.ends_with_key() { | ||
scale_decode::visitor::decode_with_visitor(bytes, ty_id, types, IgnoreVisitor) | ||
.map_err(|err| Error::Decode(err.into()))?; | ||
|
||
// Return the key bytes, having advanced the input cursor past them. | ||
let key_bytes = &before_key[before_key.len() - bytes.len()..]; | ||
Ok(Some(key_bytes)) | ||
} else { | ||
// There are no key bytes, so return None. | ||
Ok(None) | ||
} | ||
} | ||
|
||
/// Generates StorageKey implementations for tuples, e.g. | ||
/// ```rs,norun | ||
/// impl<A: EncodeAsType, B: EncodeAsType> StorageKey for (StorageKey<A>, StorageKey<B>) { | ||
|
@@ -278,11 +295,11 @@ macro_rules! impl_tuples { | |
|
||
#[rustfmt::skip] | ||
const _: () = { | ||
impl_tuples!(A iter_a 0, B iter_ab 1); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6); | ||
impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6, H iter_h 7); | ||
impl_tuples!(A iter_a 0, B iter_b 1); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6); | ||
impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6, H iter_h 7); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ | |
// see LICENSE for license details. | ||
|
||
use super::storage_address::{StorageAddress, Yes}; | ||
use super::utils::strip_storage_addess_root_bytes; | ||
use super::StorageKey; | ||
|
||
use crate::{ | ||
|
@@ -311,7 +310,8 @@ where | |
} | ||
} | ||
|
||
pub(crate) fn storage_hasher_type_id_pairs( | ||
/// Return a vec of hashers and the associated type IDs for the keys that are hashed. | ||
fn storage_hasher_type_id_pairs( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. making it not pub didn't seem to break anything offhand :D |
||
entry: &StorageEntryType, | ||
metadata: &Metadata, | ||
) -> Result<Vec<(StorageHasher, u32)>, Error> { | ||
|
@@ -357,6 +357,16 @@ pub(crate) fn storage_hasher_type_id_pairs( | |
} | ||
} | ||
|
||
/// Strips the first 16 bytes (8 for the pallet hash, 8 for the entry hash) off some storage address bytes. | ||
fn strip_storage_addess_root_bytes(address_bytes: &mut &[u8]) -> Result<(), StorageAddressError> { | ||
if address_bytes.len() >= 16 { | ||
*address_bytes = &address_bytes[16..]; | ||
Ok(()) | ||
} else { | ||
Err(StorageAddressError::UnexpectedAddressBytes) | ||
} | ||
} | ||
|
||
/// A pair of keys and values together with all the bytes that make up the storage address. | ||
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes. | ||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the functions are a part of our public API here, I tried to give them slightly nicer names