This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Implement StorageNMap #8635
Merged
Merged
Implement StorageNMap #8635
Changes from 2 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
8456583
Implement StorageNMap
KiChjang a52724a
Change copyright date to 2021
KiChjang 42513d2
Rewrite keys to use impl_for_tuples instead of recursion
KiChjang a89bf41
Implement prefix iteration on StorageNMap
KiChjang 6b17d09
Implement EncodeLike for key arguments
KiChjang 09b34ab
Rename KeyGenerator::Arg to KeyGenerator::KArg
KiChjang 976e337
Support StorageNMap in decl_storage and #[pallet::storage] macros
KiChjang 09be192
Use StorageNMap in assets pallet
KiChjang 54b38e7
Support migrate_keys in StorageNMap
KiChjang a3b48a7
Reduce line characters on select files
KiChjang 69fbed3
Refactor crate imports in decl_storage macros
KiChjang d5598e5
Some more line char reductions and doc comment update
KiChjang bbf8459
Update UI test expectations
KiChjang 9fbf1ad
Revert whitespace changes to untouched files
KiChjang 275f5a5
Merge branch 'master' into pr/8635
shawntabrizi 7686c7b
Generate Key struct instead of a 1-tuple when only 1 pair of key and …
KiChjang b83dbf2
Revert formatting changes to unrelated files
KiChjang 994e170
Introduce KeyGeneratorInner
KiChjang c3e4b9f
Add tests for StorageNMap in FRAMEv2 pallet macro
KiChjang 55c8544
Small fixes to unit tests for StorageNMap
KiChjang c0b1516
Bump runtime metadata version
KiChjang fdc3e13
Remove unused import
KiChjang c041a6c
Update tests to use runtime metadata v13
KiChjang ce13f5e
Introduce and use EncodeLikeTuple as a trait bound for KArg
KiChjang 1aeda76
Add some rustdocs
KiChjang 3671857
Revert usage of StorageNMap in assets pallet
KiChjang 707cf45
Make use of ext::PunctuatedTrailing
KiChjang 6e264ef
Merge remote-tracking branch 'upstream/master' into storage-n-map
KiChjang e8f9740
Merge remote-tracking branch 'upstream/master' into storage-n-map
KiChjang 2ff4de0
Add rustdoc for final_hash
KiChjang f75bc5a
Fix StorageNMap proc macro expansions for single key cases
KiChjang 02b7ce3
Create associated const in KeyGenerator for hasher metadata
KiChjang e142267
Refactor code according to comments from Basti
KiChjang dbcb1f6
Add module docs for generator/nmap.rs
KiChjang d207d77
Re-export storage::Key as NMapKey in pallet prelude
KiChjang f259f46
Seal the EncodeLikeTuple trait
KiChjang b081885
Extract sealing code out of key.rs
KiChjang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,342 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2021 Parity Technologies (UK) Ltd. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #[cfg(not(feature = "std"))] | ||
| use sp_std::prelude::*; | ||
| use sp_std::borrow::Borrow; | ||
| use codec::{FullCodec, Decode, Encode, EncodeLike}; | ||
| use crate::{ | ||
| storage::{ | ||
| self, unhashed, | ||
| types::{KeyGenerator, KeyHasherGenerator, ReversibleKeyGenerator}, | ||
| StorageAppend, PrefixIterator | ||
| }, | ||
| Never, hash::{StorageHasher, Twox128}, | ||
| }; | ||
|
|
||
| /// Generator for `StorageNMap` used by `decl_storage`. | ||
|
KiChjang marked this conversation as resolved.
Outdated
|
||
| /// | ||
| /// By default each key value is stored at: | ||
| /// ```nocompile | ||
| /// Twox128(module_prefix) ++ Twox128(storage_prefix) | ||
|
KiChjang marked this conversation as resolved.
Outdated
|
||
| /// ++ Hasher1(encode(key1)) ++ Hasher2(encode(key2)) ++ ... ++ HasherN(encode(keyN)) | ||
| /// ``` | ||
| /// | ||
| /// # Warning | ||
| /// | ||
| /// If the keys are not trusted (e.g. can be set by a user), a cryptographic `hasher` such as | ||
| /// `blake2_256` must be used. Otherwise, other values in storage with the same prefix can | ||
| /// be compromised. | ||
| pub trait StorageNMap<K: KeyGenerator, V: FullCodec> { | ||
| /// The type that get/take returns. | ||
| type Query; | ||
|
|
||
| /// Module prefix. Used for generating final key. | ||
| fn module_prefix() -> &'static [u8]; | ||
|
|
||
| /// Storage prefix. Used for generating final key. | ||
| fn storage_prefix() -> &'static [u8]; | ||
|
|
||
| /// The full prefix; just the hash of `module_prefix` concatenated to the hash of | ||
| /// `storage_prefix`. | ||
| fn prefix_hash() -> Vec<u8> { | ||
| let module_prefix_hashed = Twox128::hash(Self::module_prefix()); | ||
| let storage_prefix_hashed = Twox128::hash(Self::storage_prefix()); | ||
|
|
||
| let mut result = Vec::with_capacity( | ||
| module_prefix_hashed.len() + storage_prefix_hashed.len() | ||
| ); | ||
|
|
||
| result.extend_from_slice(&module_prefix_hashed[..]); | ||
| result.extend_from_slice(&storage_prefix_hashed[..]); | ||
|
|
||
| result | ||
| } | ||
|
|
||
| /// Convert an optional value retrieved from storage to the type queried. | ||
| fn from_optional_value_to_query(v: Option<V>) -> Self::Query; | ||
|
|
||
| /// Convert a query to an optional value into storage. | ||
| fn from_query_to_optional_value(v: Self::Query) -> Option<V>; | ||
|
|
||
| /// Generate a partial key used in top storage. | ||
| fn storage_n_map_partial_key<KG, KArg>(key: (KArg, KG::NextKey)) -> Vec<u8> | ||
| where | ||
| KG: KeyGenerator, | ||
| KArg: EncodeLike<KG::CurrentKey>, | ||
| { | ||
| let module_prefix_hashed = Twox128::hash(Self::module_prefix()); | ||
| let storage_prefix_hashed = Twox128::hash(Self::storage_prefix()); | ||
| let key_hashed = KG::final_key(&key); | ||
|
|
||
| let mut final_key = Vec::with_capacity( | ||
| module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len() | ||
| ); | ||
|
|
||
| final_key.extend_from_slice(&module_prefix_hashed[..]); | ||
| final_key.extend_from_slice(&storage_prefix_hashed[..]); | ||
| final_key.extend_from_slice(key_hashed.as_ref()); | ||
|
|
||
| final_key | ||
| } | ||
|
|
||
| /// Generate the full key used in top storage. | ||
| fn storage_n_map_final_key<KArg: EncodeLike<K::CurrentKey>>(key: &(KArg, K::NextKey)) -> Vec<u8> { | ||
| let module_prefix_hashed = Twox128::hash(Self::module_prefix()); | ||
| let storage_prefix_hashed = Twox128::hash(Self::storage_prefix()); | ||
| let key_hashed = K::final_key(key); | ||
|
|
||
| let mut final_key = Vec::with_capacity( | ||
| module_prefix_hashed.len() + storage_prefix_hashed.len() + key_hashed.len() | ||
| ); | ||
|
|
||
| final_key.extend_from_slice(&module_prefix_hashed[..]); | ||
| final_key.extend_from_slice(&storage_prefix_hashed[..]); | ||
| final_key.extend_from_slice(key_hashed.as_ref()); | ||
|
|
||
| final_key | ||
| } | ||
| } | ||
|
|
||
| impl<K, V, G> storage::StorageNMap<K, V> for G | ||
| where | ||
| K: KeyGenerator, | ||
| V: FullCodec, | ||
| G: StorageNMap<K, V>, | ||
| { | ||
| type Query = G::Query; | ||
|
|
||
| fn hashed_key_for<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) -> Vec<u8> { | ||
| Self::storage_n_map_final_key(&key) | ||
| } | ||
|
|
||
| fn contains_key<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) -> bool { | ||
| unhashed::exists(&Self::storage_n_map_final_key(&key)) | ||
| } | ||
|
|
||
| fn get<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) -> Self::Query { | ||
| G::from_optional_value_to_query(unhashed::get(&Self::storage_n_map_final_key(&key))) | ||
| } | ||
|
|
||
| fn try_get<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) -> Result<V, ()> { | ||
| unhashed::get(&Self::storage_n_map_final_key(&key)).ok_or(()) | ||
| } | ||
|
|
||
| fn take<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) -> Self::Query { | ||
| let final_key = Self::storage_n_map_final_key(&key); | ||
|
|
||
| let value = unhashed::take(&final_key); | ||
| G::from_optional_value_to_query(value) | ||
| } | ||
|
|
||
| fn swap<KArg1, KArg2, KOther>(key1: (KArg1, K::NextKey), key2: (KArg2, KOther::NextKey)) | ||
| where | ||
| KOther: KeyGenerator, | ||
| KArg1: EncodeLike<K::CurrentKey>, | ||
| KArg2: EncodeLike<KOther::CurrentKey>, | ||
| { | ||
| let final_x_key = Self::storage_n_map_final_key(&key1); | ||
| // Not really a partial key, but only this function allows us to specify a foreign key generator | ||
| let final_y_key = Self::storage_n_map_partial_key::<KOther, KArg2>(key2); | ||
|
|
||
| let v1 = unhashed::get_raw(&final_x_key); | ||
| if let Some(val) = unhashed::get_raw(&final_y_key) { | ||
| unhashed::put_raw(&final_x_key, &val); | ||
| } else { | ||
| unhashed::kill(&final_x_key); | ||
| } | ||
| if let Some(val) = v1 { | ||
| unhashed::put_raw(&final_y_key, &val); | ||
| } else { | ||
| unhashed::kill(&final_y_key); | ||
| } | ||
| } | ||
|
|
||
| fn insert<KArg, VArg>(key: (KArg, K::NextKey), val: VArg) | ||
| where | ||
| KArg: EncodeLike<K::CurrentKey>, | ||
| VArg: EncodeLike<V>, | ||
| { | ||
| unhashed::put(&Self::storage_n_map_final_key(&key), &val.borrow()); | ||
| } | ||
|
|
||
| fn remove<KArg: EncodeLike<K::CurrentKey>>(key: (KArg, K::NextKey)) { | ||
| unhashed::kill(&Self::storage_n_map_final_key(&key)); | ||
| } | ||
|
|
||
| fn remove_prefix<KG, KArg>(partial_key: (KArg, KG::NextKey)) | ||
| where | ||
| KG: KeyGenerator, | ||
| KArg: ?Sized + EncodeLike<KG::CurrentKey>, | ||
| { | ||
| unhashed::kill_prefix(&Self::storage_n_map_partial_key::<KG, KArg>(partial_key)); | ||
| } | ||
|
|
||
| fn iter_prefix_values<KG, KArg>(partial_key: (KArg, KG::NextKey)) -> PrefixIterator<V> | ||
| where | ||
| KG: KeyGenerator, | ||
| KArg: ?Sized + EncodeLike<KG::CurrentKey>, | ||
| { | ||
| let prefix = Self::storage_n_map_partial_key::<KG, KArg>(partial_key); | ||
| PrefixIterator { | ||
| prefix: prefix.clone(), | ||
| previous_key: prefix, | ||
| drain: false, | ||
| closure: |_raw_key, mut raw_value| V::decode(&mut raw_value), | ||
| } | ||
| } | ||
|
|
||
| fn mutate<KArg: EncodeLike<K::CurrentKey>, R, F: FnOnce(&mut Self::Query) -> R>( | ||
| key: (KArg, K::NextKey), | ||
| f: F, | ||
| ) -> R { | ||
| Self::try_mutate(key, |v| Ok::<R, Never>(f(v))).expect("`Never` can not be constructed; qed") | ||
| } | ||
|
|
||
| fn try_mutate<KArg: EncodeLike<K::CurrentKey>, R, E, F: FnOnce(&mut Self::Query) -> Result<R, E>>( | ||
| key: (KArg, K::NextKey), | ||
| f: F, | ||
| ) -> Result<R, E> { | ||
| let final_key = Self::storage_n_map_final_key(&key); | ||
| let mut val = G::from_optional_value_to_query(unhashed::get(final_key.as_ref())); | ||
|
|
||
| let ret = f(&mut val); | ||
| if ret.is_ok() { | ||
| match G::from_query_to_optional_value(val) { | ||
| Some(ref val) => unhashed::put(final_key.as_ref(), val), | ||
| None => unhashed::kill(final_key.as_ref()), | ||
| } | ||
| } | ||
| ret | ||
| } | ||
|
|
||
| fn mutate_exists<KArg: EncodeLike<K::CurrentKey>, R, F: FnOnce(&mut Option<V>) -> R>( | ||
| key: (KArg, K::NextKey), | ||
| f: F, | ||
| ) -> R { | ||
| Self::try_mutate_exists(key, |v| Ok::<R, Never>(f(v))).expect("`Never` can not be constructed; qed") | ||
| } | ||
|
|
||
| fn try_mutate_exists<KArg: EncodeLike<K::CurrentKey>, R, E, F: FnOnce(&mut Option<V>) -> Result<R, E>>( | ||
| key: (KArg, K::NextKey), | ||
| f: F, | ||
| ) -> Result<R, E> { | ||
| let final_key = Self::storage_n_map_final_key(&key); | ||
| let mut val = unhashed::get(final_key.as_ref()); | ||
|
|
||
| let ret = f(&mut val); | ||
| if ret.is_ok() { | ||
| match val { | ||
| Some(ref val) => unhashed::put(final_key.as_ref(), val), | ||
| None => unhashed::kill(final_key.as_ref()), | ||
| } | ||
| } | ||
| ret | ||
| } | ||
|
|
||
| fn append<Item, EncodeLikeItem, KArg>(key: (KArg, K::NextKey), item: EncodeLikeItem) | ||
| where | ||
| KArg: EncodeLike<K::CurrentKey>, | ||
| Item: Encode, | ||
| EncodeLikeItem: EncodeLike<Item>, | ||
| V: StorageAppend<Item> | ||
| { | ||
| let final_key = Self::storage_n_map_final_key(&key); | ||
| sp_io::storage::append(&final_key, item.encode()); | ||
| } | ||
|
|
||
| fn migrate_keys<OldHasher, KArg>(key: (KArg, K::NextKey)) -> Option<V> | ||
| where | ||
| OldHasher: KeyHasherGenerator, | ||
| KArg: EncodeLike<K::CurrentKey> | ||
| { | ||
| let old_key = { | ||
| let module_prefix_hashed = Twox128::hash(Self::module_prefix()); | ||
| let storage_prefix_hashed = Twox128::hash(Self::storage_prefix()); | ||
| let key_hashed = K::final_hash::<KArg, OldHasher>(&key); | ||
|
|
||
| let mut final_key = Vec::with_capacity( | ||
| module_prefix_hashed.len() | ||
| + storage_prefix_hashed.len() | ||
| + key_hashed.len() | ||
| ); | ||
|
|
||
| final_key.extend_from_slice(&module_prefix_hashed[..]); | ||
| final_key.extend_from_slice(&storage_prefix_hashed[..]); | ||
| final_key.extend_from_slice(key_hashed.as_ref()); | ||
|
|
||
| final_key | ||
| }; | ||
| unhashed::take(old_key.as_ref()).map(|value| { | ||
| unhashed::put(Self::storage_n_map_final_key(&key).as_ref(), &value); | ||
| value | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl<K: ReversibleKeyGenerator, V: FullCodec, G: StorageNMap<K, V>> storage::IterableStorageNMap<K, V> for G { | ||
| type Iterator = PrefixIterator<((K::CurrentKey, K::NextKey), V)>; | ||
|
|
||
| fn iter() -> Self::Iterator { | ||
| let prefix = G::prefix_hash(); | ||
| Self::Iterator { | ||
| prefix: prefix.clone(), | ||
| previous_key: prefix, | ||
| drain: false, | ||
| closure: |raw_key_without_prefix, mut raw_value| { | ||
| let final_key = K::decode_final_key(raw_key_without_prefix)?; | ||
| Ok((final_key, V::decode(&mut raw_value)?)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn drain() -> Self::Iterator { | ||
| let mut iterator = Self::iter(); | ||
| iterator.drain = true; | ||
| iterator | ||
| } | ||
|
|
||
| fn translate<O: Decode, F: FnMut((K::CurrentKey, K::NextKey), O) -> Option<V>>(mut f: F) { | ||
| let prefix = G::prefix_hash(); | ||
| let mut previous_key = prefix.clone(); | ||
| while let Some(next) = sp_io::storage::next_key(&previous_key) | ||
| .filter(|n| n.starts_with(&prefix)) | ||
| { | ||
| previous_key = next; | ||
| let value = match unhashed::get::<O>(&previous_key) { | ||
| Some(value) => value, | ||
| None => { | ||
| log::error!("Invalid translate: fail to decode old value"); | ||
| continue | ||
| }, | ||
| }; | ||
|
|
||
| let final_key = match K::decode_final_key(&previous_key[prefix.len()..]) { | ||
| Ok(final_key) => final_key, | ||
| Err(_) => { | ||
| log::error!("Invalid translate: fail to decode key"); | ||
| continue | ||
| } | ||
| }; | ||
|
|
||
| match f(final_key, value) { | ||
| Some(new) => unhashed::put::<V>(&previous_key, &new), | ||
| None => unhashed::kill(&previous_key), | ||
| } | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.