git.delta.rocks / unique-network / refs/commits / e8fdadb89923

difftreelog

source

pallets/contracts/src/storage.rs14.4 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! This module contains routines for accessing and altering a contract related state.1920use crate::{21	exec::{AccountIdOf, StorageKey},22	BalanceOf, CodeHash, ContractInfoOf, Config, TrieId, AccountCounter, DeletionQueue, Error,23	weights::WeightInfo,24};25use codec::{Codec, Encode, Decode};26use sp_std::prelude::*;27use sp_std::{marker::PhantomData, fmt::Debug};28use sp_io::hashing::blake2_256;29use sp_runtime::{30	RuntimeDebug,31	traits::{Bounded, Saturating, Zero, Hash, Member, MaybeSerializeDeserialize},32};33use sp_core::crypto::UncheckedFrom;34use frame_support::{35	dispatch::{DispatchError, DispatchResult},36	storage::child::{self, KillChildStorageResult, ChildInfo},37	traits::Get,38	weights::Weight,39};4041pub type AliveContractInfo<T> =42	RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;43pub type TombstoneContractInfo<T> = RawTombstoneContractInfo<44	<T as frame_system::Config>::Hash,45	<T as frame_system::Config>::Hashing,46>;4748/// Information for managing an account and its sub trie abstraction.49/// This is the required info to cache for an account50#[derive(Encode, Decode, RuntimeDebug)]51pub enum ContractInfo<T: Config> {52	Alive(AliveContractInfo<T>),53	Tombstone(TombstoneContractInfo<T>),54}5556impl<T: Config> ContractInfo<T> {57	/// If contract is alive then return some alive info58	pub fn get_alive(self) -> Option<AliveContractInfo<T>> {59		if let ContractInfo::Alive(alive) = self {60			Some(alive)61		} else {62			None63		}64	}65	/// If contract is alive then return some reference to alive info66	pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {67		if let ContractInfo::Alive(ref alive) = self {68			Some(alive)69		} else {70			None71		}72	}7374	/// If contract is tombstone then return some tombstone info75	pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {76		if let ContractInfo::Tombstone(tombstone) = self {77			Some(tombstone)78		} else {79			None80		}81	}82}8384/// Information for managing an account and its sub trie abstraction.85/// This is the required info to cache for an account.86#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]87pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {88	/// Unique ID for the subtree encoded as a bytes vector.89	pub trie_id: TrieId,90	/// The total number of bytes used by this contract.91	///92	/// It is a sum of each key-value pair stored by this contract.93	pub storage_size: u32,94	/// The total number of key-value pairs in storage of this contract.95	pub pair_count: u32,96	/// The code associated with a given account.97	pub code_hash: CodeHash,98	/// Pay rent at most up to this value.99	pub rent_allowance: Balance,100	/// The amount of rent that was payed by the contract over its whole lifetime.101	///102	/// A restored contract starts with a value of zero just like a new contract.103	pub rent_payed: Balance,104	/// Last block rent has been payed.105	pub deduct_block: BlockNumber,106	/// Last block child storage has been written.107	pub last_write: Option<BlockNumber>,108	/// This field is reserved for future evolution of format.109	pub _reserved: Option<()>,110}111112impl<CodeHash, Balance, BlockNumber> RawAliveContractInfo<CodeHash, Balance, BlockNumber> {113	/// Associated child trie unique id is built from the hash part of the trie id.114	pub fn child_trie_info(&self) -> ChildInfo {115		child_trie_info(&self.trie_id[..])116	}117}118119/// Associated child trie unique id is built from the hash part of the trie id.120fn child_trie_info(trie_id: &[u8]) -> ChildInfo {121	ChildInfo::new_default(trie_id)122}123124#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)]125pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);126127impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>128where129	H: Member130		+ MaybeSerializeDeserialize131		+ Debug132		+ AsRef<[u8]>133		+ AsMut<[u8]>134		+ Copy135		+ Default136		+ sp_std::hash::Hash137		+ Codec,138	Hasher: Hash<Output = H>,139{140	pub fn new(storage_root: &[u8], code_hash: H) -> Self {141		let mut buf = Vec::new();142		storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));143		buf.extend_from_slice(code_hash.as_ref());144		RawTombstoneContractInfo(<Hasher as Hash>::hash(&buf[..]), PhantomData)145	}146}147148impl<T: Config> From<AliveContractInfo<T>> for ContractInfo<T> {149	fn from(alive_info: AliveContractInfo<T>) -> Self {150		Self::Alive(alive_info)151	}152}153154/// An error that means that the account requested either doesn't exist or represents a tombstone155/// account.156#[cfg_attr(test, derive(PartialEq, Eq, Debug))]157pub struct ContractAbsentError;158159#[derive(Encode, Decode)]160pub struct DeletedContract {161	pair_count: u32,162	trie_id: TrieId,163}164165pub struct Storage<T>(PhantomData<T>);166167impl<T> Storage<T>168where169	T: Config,170	T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,171{172	/// Reads a storage kv pair of a contract.173	///174	/// The read is performed from the `trie_id` only. The `address` is not necessary. If the contract175	/// doesn't store under the given `key` `None` is returned.176	pub fn read(trie_id: &TrieId, key: &StorageKey) -> Option<Vec<u8>> {177		child::get_raw(&child_trie_info(&trie_id), &blake2_256(key))178	}179180	/// Update a storage entry into a contract's kv storage.181	///182	/// If the `opt_new_value` is `None` then the kv pair is removed.183	///184	/// This function also updates the bookkeeping info such as: number of total non-empty pairs a185	/// contract owns, the last block the storage was written to, etc. That's why, in contrast to186	/// `read`, this function also requires the `account` ID.187	///188	/// If the contract specified by the id `account` doesn't exist `Err` is returned.`189	///190	/// # Panics191	///192	/// Panics iff the `account` specified is not alive and in storage.193	pub fn write(194		account: &AccountIdOf<T>,195		trie_id: &TrieId,196		key: &StorageKey,197		opt_new_value: Option<Vec<u8>>,198	) -> DispatchResult {199		let mut new_info = match <ContractInfoOf<T>>::get(account) {200			Some(ContractInfo::Alive(alive)) => alive,201			None | Some(ContractInfo::Tombstone(_)) => panic!("Contract not found"),202		};203204		let hashed_key = blake2_256(key);205		let child_trie_info = &child_trie_info(&trie_id);206207		let opt_prev_len = child::len(&child_trie_info, &hashed_key);208209		// Update the total number of KV pairs and the number of empty pairs.210		match (&opt_prev_len, &opt_new_value) {211			(Some(_), None) => {212				new_info.pair_count = new_info213					.pair_count214					.checked_sub(1)215					.ok_or_else(|| Error::<T>::StorageExhausted)?;216			}217			(None, Some(_)) => {218				new_info.pair_count = new_info219					.pair_count220					.checked_add(1)221					.ok_or_else(|| Error::<T>::StorageExhausted)?;222			}223			(Some(_), Some(_)) => {}224			(None, None) => {}225		}226227		// Update the total storage size.228		let prev_value_len = opt_prev_len.unwrap_or(0);229		let new_value_len = opt_new_value230			.as_ref()231			.map(|new_value| new_value.len() as u32)232			.unwrap_or(0);233		new_info.storage_size = new_info234			.storage_size235			.checked_sub(prev_value_len)236			.and_then(|val| val.checked_add(new_value_len))237			.ok_or_else(|| Error::<T>::StorageExhausted)?;238239		new_info.last_write = Some(<frame_system::Pallet<T>>::block_number());240		<ContractInfoOf<T>>::insert(&account, ContractInfo::Alive(new_info));241242		// Finally, perform the change on the storage.243		match opt_new_value {244			Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]),245			None => child::kill(&child_trie_info, &hashed_key),246		}247248		Ok(())249	}250251	/// Returns the rent allowance set for the contract give by the account id.252	pub fn rent_allowance(account: &AccountIdOf<T>) -> Result<BalanceOf<T>, ContractAbsentError> {253		<ContractInfoOf<T>>::get(account)254			.and_then(|i| i.as_alive().map(|i| i.rent_allowance))255			.ok_or(ContractAbsentError)256	}257258	/// Set the rent allowance for the contract given by the account id.259	///260	/// Returns `Err` if the contract doesn't exist or is a tombstone.261	pub fn set_rent_allowance(262		account: &AccountIdOf<T>,263		rent_allowance: BalanceOf<T>,264	) -> Result<(), ContractAbsentError> {265		<ContractInfoOf<T>>::mutate(account, |maybe_contract_info| match maybe_contract_info {266			Some(ContractInfo::Alive(ref mut alive_info)) => {267				alive_info.rent_allowance = rent_allowance;268				Ok(())269			}270			_ => Err(ContractAbsentError),271		})272	}273274	/// Creates a new contract descriptor in the storage with the given code hash at the given address.275	///276	/// Returns `Err` if there is already a contract (or a tombstone) exists at the given address.277	pub fn place_contract(278		account: &AccountIdOf<T>,279		trie_id: TrieId,280		ch: CodeHash<T>,281	) -> Result<AliveContractInfo<T>, DispatchError> {282		<ContractInfoOf<T>>::try_mutate(account, |existing| {283			if existing.is_some() {284				return Err(Error::<T>::DuplicateContract.into());285			}286287			let contract = AliveContractInfo::<T> {288				code_hash: ch,289				storage_size: 0,290				trie_id,291				deduct_block:292					// We want to charge rent for the first block in advance. Therefore we293					// treat the contract as if it was created in the last block and then294					// charge rent for it during instantiation.295					<frame_system::Pallet<T>>::block_number().saturating_sub(1u32.into()),296				rent_allowance: <BalanceOf<T>>::max_value(),297				rent_payed: <BalanceOf<T>>::zero(),298				pair_count: 0,299				last_write: None,300				_reserved: None,301			};302303			*existing = Some(contract.clone().into());304305			Ok(contract)306		})307	}308309	/// Push a contract's trie to the deletion queue for lazy removal.310	///311	/// You must make sure that the contract is also removed or converted into a tombstone312	/// when queuing the trie for deletion.313	pub fn queue_trie_for_deletion(contract: &AliveContractInfo<T>) -> DispatchResult {314		if <DeletionQueue<T>>::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize {315			Err(Error::<T>::DeletionQueueFull.into())316		} else {317			<DeletionQueue<T>>::append(DeletedContract {318				pair_count: contract.pair_count,319				trie_id: contract.trie_id.clone(),320			});321			Ok(())322		}323	}324325	/// Calculates the weight that is necessary to remove one key from the trie and how many326	/// of those keys can be deleted from the deletion queue given the supplied queue length327	/// and weight limit.328	pub fn deletion_budget(queue_len: usize, weight_limit: Weight) -> (u64, u32) {329		let base_weight = T::WeightInfo::on_initialize();330		let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1)331			- T::WeightInfo::on_initialize_per_queue_item(0);332		let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1)333			- T::WeightInfo::on_initialize_per_trie_key(0);334		let decoding_weight = weight_per_queue_item.saturating_mul(queue_len as Weight);335336		// `weight_per_key` being zero makes no sense and would constitute a failure to337		// benchmark properly. We opt for not removing any keys at all in this case.338		let key_budget = weight_limit339			.saturating_sub(base_weight)340			.saturating_sub(decoding_weight)341			.checked_div(weight_per_key)342			.unwrap_or(0) as u32;343344		(weight_per_key, key_budget)345	}346347	/// Delete as many items from the deletion queue possible within the supplied weight limit.348	///349	/// It returns the amount of weight used for that task or `None` when no weight was used350	/// apart from the base weight.351	pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight {352		let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);353		if queue_len == 0 {354			return weight_limit;355		}356357		let (weight_per_key, mut remaining_key_budget) =358			Self::deletion_budget(queue_len, weight_limit);359360		// We want to check whether we have enough weight to decode the queue before361		// proceeding. Too little weight for decoding might happen during runtime upgrades362		// which consume the whole block before the other `on_initialize` blocks are called.363		if remaining_key_budget == 0 {364			return weight_limit;365		}366367		let mut queue = <DeletionQueue<T>>::get();368369		while !queue.is_empty() && remaining_key_budget > 0 {370			// Cannot panic due to loop condition371			let trie = &mut queue[0];372			let pair_count = trie.pair_count;373			let outcome =374				child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));375			if pair_count > remaining_key_budget {376				// Cannot underflow because of the if condition377				trie.pair_count -= remaining_key_budget;378			} else {379				// We do not care to preserve order. The contract is deleted already and380				// noone waits for the trie to be deleted.381				let removed = queue.swap_remove(0);382				match outcome {383					// This should not happen as our budget was large enough to remove all keys.384					KillChildStorageResult::SomeRemaining(_) => {385						log::error!(386							target: "runtime::contracts",387							"After deletion keys are remaining in this child trie: {:?}",388							removed.trie_id,389						);390					}391					KillChildStorageResult::AllRemoved(_) => (),392				}393			}394			remaining_key_budget =395				remaining_key_budget.saturating_sub(remaining_key_budget.min(pair_count));396		}397398		<DeletionQueue<T>>::put(queue);399		weight_limit.saturating_sub(weight_per_key.saturating_mul(remaining_key_budget as Weight))400	}401402	/// This generator uses inner counter for account id and applies the hash over `AccountId +403	/// accountid_counter`.404	pub fn generate_trie_id(account_id: &AccountIdOf<T>) -> TrieId {405		// Note that skipping a value due to error is not an issue here.406		// We only need uniqueness, not sequence.407		let new_seed = <AccountCounter<T>>::mutate(|v| {408			*v = v.wrapping_add(1);409			*v410		});411412		let buf: Vec<_> = account_id413			.as_ref()414			.iter()415			.chain(&new_seed.to_le_bytes())416			.cloned()417			.collect();418		T::Hashing::hash(&buf).as_ref().into()419	}420421	/// Returns the code hash of the contract specified by `account` ID.422	#[cfg(test)]423	pub fn code_hash(account: &AccountIdOf<T>) -> Result<CodeHash<T>, ContractAbsentError> {424		<ContractInfoOf<T>>::get(account)425			.and_then(|i| i.as_alive().map(|i| i.code_hash))426			.ok_or(ContractAbsentError)427	}428429	/// Fill up the queue in order to exercise the limits during testing.430	#[cfg(test)]431	pub fn fill_queue_with_dummies() {432		let queue: Vec<_> = (0..T::DeletionQueueDepth::get())433			.map(|_| DeletedContract {434				pair_count: 0,435				trie_id: vec![],436			})437			.collect();438		<DeletionQueue<T>>::put(queue);439	}440}