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
Pallet Preimage #10153
Closed
Closed
Pallet Preimage #10153
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
3beae17
initial idea
shawntabrizi 3f03348
more
shawntabrizi 5c23c8e
fix compile
shawntabrizi 0f80111
add clear and request logic
shawntabrizi 6fd3ccf
improve some docs
shawntabrizi 8847f9f
Add and implement trait
shawntabrizi ccd6bb8
continuing to improve
shawntabrizi ec2b0f0
refcount type
shawntabrizi cb512ef
infallible system preimage upload
shawntabrizi 7dc3b43
fmt
shawntabrizi d1621fb
fix requests
shawntabrizi 9075771
Merge branch 'master' into shawntabrizi-preimage-pallet
shawntabrizi b9f64da
Make it simple
gavofyork 23485e7
Make it simple
gavofyork 6679e5b
Formatting
gavofyork 3cce356
Merge branch 'master' into shawntabrizi-preimage-pallet
shawntabrizi 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,38 @@ | ||
| [package] | ||
| name = "pallet-preimage" | ||
| version = "4.0.0-dev" | ||
| authors = ["Parity Technologies <admin@parity.io>"] | ||
| edition = "2018" | ||
| license = "Apache-2.0" | ||
| homepage = "https://substrate.io" | ||
| repository = "https://github.com/paritytech/substrate/" | ||
| description = "FRAME pallet for storing preimages of hashes" | ||
| readme = "README.md" | ||
|
|
||
| [package.metadata.docs.rs] | ||
| targets = ["x86_64-unknown-linux-gnu"] | ||
|
|
||
| [dependencies] | ||
| codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } | ||
| scale-info = { version = "1.0", default-features = false, features = ["derive"] } | ||
| sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } | ||
| sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" } | ||
| sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../primitives/runtime" } | ||
| frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } | ||
| frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } | ||
|
|
||
| [dev-dependencies] | ||
| sp-core = { version = "4.0.0-dev", path = "../../primitives/core" } | ||
|
|
||
| [features] | ||
| default = ["std"] | ||
| std = [ | ||
| "codec/std", | ||
| "scale-info/std", | ||
| "sp-std/std", | ||
| "sp-io/std", | ||
| "sp-runtime/std", | ||
| "frame-support/std", | ||
| "frame-system/std", | ||
| ] | ||
| try-runtime = ["frame-support/try-runtime"] |
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,331 @@ | ||
| // This file is part of Substrate. | ||
|
|
||
| // Copyright (C) 2017-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. | ||
|
|
||
| //! # Preimage Pallet | ||
| //! | ||
| //! - [`Config`] | ||
| //! - [`Call`] | ||
| //! | ||
| //! ## Overview | ||
| //! | ||
| //! The Preimage pallet allows for the users and the runtime to store the preimage | ||
| //! of a hash on chain. This can be used by other pallets where storing and managing | ||
| //! large byte-blobs. | ||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| use sp_runtime::traits::{BadOrigin, Hash, Saturating}; | ||
| use sp_std::{convert::TryFrom, prelude::*}; | ||
|
|
||
| use codec::{Decode, Encode, MaxEncodedLen}; | ||
| use frame_support::{ | ||
| ensure, | ||
| pallet_prelude::Get, | ||
| traits::{Currency, PreimageProvider, PreimageRecipient, ReservableCurrency}, | ||
| weights::Pays, | ||
| BoundedVec, | ||
| }; | ||
| use scale_info::TypeInfo; | ||
|
|
||
| use frame_support::pallet_prelude::*; | ||
| use frame_system::pallet_prelude::*; | ||
|
|
||
| pub use pallet::*; | ||
|
|
||
| /// A type to note whether a preimage is owned by a user or the system. | ||
| #[derive(Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] | ||
| pub enum RequestStatus<AccountId, Balance> { | ||
| /// The associated preimage has not yet been requested by the system. The given deposit (if | ||
| /// some) is being held until either it becomes requested or the user retracts the primage. | ||
| Unrequested(Option<(AccountId, Balance)>), | ||
| /// There are a non-zero number of outstanding requests for this hash by this chain. If there | ||
| /// is a preimage registered, then it may be removed iff this counter becomes zero. | ||
| Requested(u32), | ||
| } | ||
|
|
||
| type BalanceOf<T> = | ||
| <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; | ||
|
|
||
| #[frame_support::pallet] | ||
| pub mod pallet { | ||
| use super::*; | ||
|
|
||
| #[pallet::config] | ||
| pub trait Config: frame_system::Config { | ||
| /// The overarching event type. | ||
| type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; | ||
|
|
||
| /// Currency type for this pallet. | ||
| type Currency: ReservableCurrency<Self::AccountId>; | ||
|
|
||
| /// An origin that can request a preimage be placed on-chain without a deposit or fee, or | ||
| /// manage existing preimages. | ||
| type ManagerOrigin: EnsureOrigin<Self::Origin>; | ||
|
|
||
| /// Max size allowed for a preimage. | ||
| type MaxSize: Get<u32>; | ||
|
|
||
| /// The base deposit for placing a preimage on chain. | ||
| type BaseDeposit: Get<BalanceOf<Self>>; | ||
|
|
||
| /// The per-byte deposit for placing a preimage on chain. | ||
| type ByteDeposit: Get<BalanceOf<Self>>; | ||
| } | ||
|
|
||
| #[pallet::pallet] | ||
| #[pallet::generate_store(pub(super) trait Store)] | ||
| #[pallet::generate_storage_info] | ||
| pub struct Pallet<T>(PhantomData<T>); | ||
|
|
||
| #[pallet::event] | ||
| #[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
| pub enum Event<T: Config> { | ||
| /// A preimage has been noted. | ||
| Noted { hash: T::Hash }, | ||
| /// A preimage has been requested. | ||
| Requested { hash: T::Hash }, | ||
| /// A preimage has ben cleared. | ||
| Cleared { hash: T::Hash }, | ||
| } | ||
|
|
||
| #[pallet::error] | ||
| pub enum Error<T> { | ||
| /// Preimage is too large to store on-chain. | ||
| TooLarge, | ||
| /// Preimage has already been noted on-chain. | ||
| AlreadyNoted, | ||
| /// The user is not authorized to perform this action. | ||
| NotAuthorized, | ||
| /// The preimage cannot be removed since it has not yet been noted. | ||
| NotNoted, | ||
| /// A preimage may not be removed when there are outstanding requests. | ||
| Requested, | ||
| /// The preimage request cannot be removed since no outstanding requests exist. | ||
| NotRequested, | ||
| } | ||
|
|
||
| /// The request status of a given hash. | ||
| #[pallet::storage] | ||
| pub(super) type StatusFor<T: Config> = | ||
| StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, BalanceOf<T>>>; | ||
|
|
||
| /// The preimages stored by this pallet. | ||
| #[pallet::storage] | ||
| pub(super) type PreimageFor<T: Config> = | ||
| StorageMap<_, Identity, T::Hash, BoundedVec<u8, T::MaxSize>>; | ||
|
|
||
| #[pallet::call] | ||
| impl<T: Config> Pallet<T> { | ||
| /// Register a preimage on-chain. | ||
| /// | ||
| /// If the preimage was previously requested, no fees or deposits are taken for providing | ||
| /// the preimage. Otherwise, a deposit is taken proportional to the size of the preimage. | ||
| #[pallet::weight(0)] | ||
| pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo { | ||
| // We accept a signed origin which will pay a deposit, or a root origin where a deposit | ||
| // is not taken. | ||
| let maybe_sender = Self::ensure_signed_or_manager(origin)?; | ||
| let bounded_vec = | ||
| BoundedVec::<u8, T::MaxSize>::try_from(bytes).map_err(|()| Error::<T>::TooLarge)?; | ||
|
gavofyork marked this conversation as resolved.
|
||
| let system_requested = Self::note_bytes(bounded_vec, maybe_sender.as_ref())?; | ||
| if system_requested || maybe_sender.is_none() { | ||
| Ok(Pays::No.into()) | ||
| } else { | ||
| Ok(().into()) | ||
| } | ||
| } | ||
|
|
||
| /// Clear an unrequested preimage from the runtime storage. | ||
| #[pallet::weight(0)] | ||
| pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult { | ||
| let maybe_sender = Self::ensure_signed_or_manager(origin)?; | ||
| Self::unnote_preimage(hash, maybe_sender) | ||
| } | ||
|
|
||
| /// Request a preimage be uploaded to the chain without paying any fees or deposits. | ||
| /// | ||
| /// If the preimage requests has already been provided on-chain, we unreserve any deposit | ||
| /// a user may have paid, and take the control of the preimage out of their hands. | ||
| #[pallet::weight(0)] | ||
| pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult { | ||
| T::ManagerOrigin::ensure_origin(origin)?; | ||
| Self::do_request_preimage(hash); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Clear a previously made request for a preimage. | ||
| /// | ||
| /// NOTE: THIS MUST NOT BE CALLED ON `hash` MORE TIMES THAN `request_preimage`. | ||
| #[pallet::weight(0)] | ||
| pub fn clear_request(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult { | ||
| T::ManagerOrigin::ensure_origin(origin)?; | ||
| Self::do_clear_request(hash) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<T: Config> Pallet<T> { | ||
| /// Ensure that the origin is either the `ManagerOrigin` or a signed origin. | ||
| fn ensure_signed_or_manager(origin: T::Origin) -> Result<Option<T::AccountId>, BadOrigin> { | ||
| if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() { | ||
| return Ok(None) | ||
| } | ||
| let who = ensure_signed(origin)?; | ||
| Ok(Some(who)) | ||
| } | ||
|
|
||
| /// Store some preimage on chain. | ||
| /// | ||
| /// We verify that the preimage is within the bounds of what the pallet supports. | ||
| /// | ||
| /// If the preimage was requested to be uploaded, then the user pays no deposits or tx fees. | ||
| fn note_bytes( | ||
| preimage: BoundedVec<u8, T::MaxSize>, | ||
| maybe_depositor: Option<&T::AccountId>, | ||
| ) -> Result<bool, DispatchError> { | ||
| let hash = T::Hashing::hash(&preimage); | ||
| ensure!(!PreimageFor::<T>::contains_key(hash), Error::<T>::AlreadyNoted); | ||
|
|
||
| // We take a deposit only if there is a provided depositor, and the preimage was not | ||
| // previously requested. This also allows the tx to pay no fee. | ||
| let was_requested = match (StatusFor::<T>::get(hash), maybe_depositor) { | ||
| (Some(RequestStatus::Requested(_)), _) => true, | ||
| (Some(RequestStatus::Unrequested(..)), _) => Err(Error::<T>::AlreadyNoted)?, | ||
| (_, None) => true, | ||
| (None, Some(depositor)) => { | ||
| let length = preimage.len() as u32; | ||
| let deposit = T::BaseDeposit::get() | ||
| .saturating_add(T::ByteDeposit::get().saturating_mul(length.into())); | ||
| T::Currency::reserve(depositor, deposit)?; | ||
| let status = RequestStatus::Unrequested(Some((depositor.clone(), deposit))); | ||
| StatusFor::<T>::insert(hash, status); | ||
| false | ||
| }, | ||
| }; | ||
|
|
||
| PreimageFor::<T>::insert(hash, preimage); | ||
| Self::deposit_event(Event::Noted { hash }); | ||
|
|
||
| Ok(was_requested) | ||
| } | ||
|
|
||
| // This function will add a hash to the list of requested preimages. | ||
| // | ||
| // If the preimage already exists before the request is made, the deposit for the preimage is | ||
| // returned to the user, and removed from their management. | ||
| fn do_request_preimage(hash: T::Hash) { | ||
| let count = StatusFor::<T>::get(hash).map_or(1, |x| match x { | ||
| RequestStatus::Requested(mut count) => { | ||
| count.saturating_inc(); | ||
| count | ||
| }, | ||
| RequestStatus::Unrequested(None) => 1, | ||
| RequestStatus::Unrequested(Some((owner, deposit))) => { | ||
| // Return the deposit - the preimage now has outstanding requests. | ||
| T::Currency::unreserve(&owner, deposit); | ||
| 1 | ||
| }, | ||
| }); | ||
| StatusFor::<T>::insert(&hash, RequestStatus::Requested(count)); | ||
| if count == 1 { | ||
| Self::deposit_event(Event::Requested { hash }); | ||
| } | ||
| } | ||
|
|
||
| // Clear a preimage from the storage of the chain, returning any deposit that may be reserved. | ||
| // | ||
| // If `maybe_owner` is provided, we verify that it is the correct owner before clearing the | ||
| // data. | ||
| // | ||
| // If `maybe_owner` is not provided, this function cannot return an error. | ||
| fn unnote_preimage(hash: T::Hash, maybe_check_owner: Option<T::AccountId>) -> DispatchResult { | ||
| match StatusFor::<T>::get(&hash).ok_or(Error::<T>::NotNoted)? { | ||
| RequestStatus::Unrequested(Some((owner, deposit))) => { | ||
| ensure!( | ||
| maybe_check_owner.map_or(true, |c| &c == &owner), | ||
| Error::<T>::NotAuthorized | ||
| ); | ||
| T::Currency::unreserve(&owner, deposit); | ||
| }, | ||
| RequestStatus::Unrequested(None) => { | ||
| ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized); | ||
| }, | ||
| RequestStatus::Requested(_) => Err(Error::<T>::Requested)?, | ||
| } | ||
| StatusFor::<T>::remove(&hash); | ||
| PreimageFor::<T>::remove(&hash); | ||
| Self::deposit_event(Event::Cleared { hash }); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Clear a preimage request. | ||
| fn do_clear_request(hash: T::Hash) -> DispatchResult { | ||
| match StatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? { | ||
| RequestStatus::Requested(mut count) if count > 1 => { | ||
| count.saturating_dec(); | ||
| StatusFor::<T>::insert(&hash, RequestStatus::Requested(count)); | ||
| }, | ||
| RequestStatus::Requested(count) => { | ||
| debug_assert!(count == 1, "preimage request counter at zero?"); | ||
| PreimageFor::<T>::remove(&hash); | ||
| StatusFor::<T>::remove(&hash); | ||
| Self::deposit_event(Event::Cleared { hash }); | ||
| }, | ||
| RequestStatus::Unrequested(_) => Err(Error::<T>::NotRequested)?, | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> { | ||
| fn preimage_exists(hash: T::Hash) -> bool { | ||
| PreimageFor::<T>::contains_key(hash) | ||
| } | ||
|
|
||
| fn preimage_requested(hash: T::Hash) -> bool { | ||
| matches!(StatusFor::<T>::get(hash), Some(RequestStatus::Requested(..))) | ||
| } | ||
|
|
||
| fn get_preimage(hash: T::Hash) -> Option<Vec<u8>> { | ||
| PreimageFor::<T>::get(hash).map(|preimage| preimage.to_vec()) | ||
| } | ||
|
|
||
| fn request_preimage(hash: T::Hash) { | ||
| Self::do_request_preimage(hash) | ||
| } | ||
|
|
||
| fn clear_request(hash: T::Hash) { | ||
| let res = Self::do_clear_request(hash); | ||
| debug_assert!(res.is_ok(), "do_clear_request failed - counter underflow?"); | ||
| } | ||
| } | ||
|
|
||
| impl<T: Config> PreimageRecipient<T::Hash> for Pallet<T> { | ||
| type MaxSize = T::MaxSize; | ||
|
|
||
| fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) { | ||
| // We don't really care if this fails, since that's only the case if someone else has | ||
| // already noted it. | ||
| let _ = Self::note_bytes(bytes, None); | ||
| } | ||
|
|
||
| fn unnote_preimage(hash: T::Hash) { | ||
| // Should never fail if authorization check is skipped. | ||
| let res = Self::unnote_preimage(hash, None); | ||
| debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?"); | ||
| } | ||
| } | ||
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.