// This file is part of Substrate. // Copyright (C) 2018-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. //! A module that implements instrumented code cache. //! //! - In order to run contract code we need to instrument it with gas metering. //! To do that we need to provide the schedule which will supply exact gas costs values. //! We cache this code in the storage saving the schedule version. //! - Before running contract code we check if the cached code has the schedule version that //! is equal to the current saved schedule. //! If it is equal then run the code, if it isn't reinstrument with the current schedule. //! - When we update the schedule we want it to have strictly greater version than the current saved //! one: //! this guarantees that every instrumented contract code in cache cannot have the version equal to //! the current one. Thus, before executing a contract it should be reinstrument with new schedule. use crate::{ gas::{GasMeter, Token}, wasm::{prepare, PrefabWasmModule}, weights::WeightInfo, CodeHash, CodeStorage, Config, Error, Event, OwnerInfoOf, Pallet, PristineCode, Schedule, Weight, }; use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, storage::StorageMap, traits::ReservableCurrency, }; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::BadOrigin; /// Put the instrumented module in storage. /// /// Increments the refcount of the in-storage `prefab_module` if it already exists in storage /// under the specified `code_hash`. pub fn store(mut module: PrefabWasmModule, instantiated: bool) -> DispatchResult where T::AccountId: UncheckedFrom + AsRef<[u8]>, { let code_hash = sp_std::mem::take(&mut module.code_hash); >::mutate(&code_hash, |existing| match existing { Some(existing) => { // We instrument any uploaded contract anyways. We might as well store it to save // a potential re-instrumentation later. existing.code = module.code; existing.instruction_weights_version = module.instruction_weights_version; // When the code was merely uploaded but not instantiated we can skip this. if instantiated { >::mutate(&code_hash, |owner_info| { if let Some(owner_info) = owner_info { owner_info.refcount = owner_info.refcount.checked_add(1).expect( " refcount is 64bit. Generating this overflow would require to store _at least_ 18 exabyte of data assuming that a contract consumes only one byte of data. Any node would run out of storage space before hitting this overflow. qed ", ); } }) } Ok(()) }, None => { let orig_code = module.original_code.take().expect( " If an executable isn't in storage it was uploaded. If it was uploaded the original code must exist. qed ", ); let mut owner_info = module.owner_info.take().expect( "If an executable isn't in storage it was uploaded. If it was uploaded the owner info was generated and attached. qed ", ); // This `None` case happens only in freshly uploaded modules. This means that // the `owner` is always the origin of the current transaction. T::Currency::reserve(&owner_info.owner, owner_info.deposit) .map_err(|_| >::StorageDepositNotEnoughFunds)?; owner_info.refcount = if instantiated { 1 } else { 0 }; >::insert(&code_hash, orig_code); >::insert(&code_hash, owner_info); *existing = Some(module); >::deposit_event(Event::CodeStored { code_hash }); Ok(()) }, }) } /// Decrement the refcount of a code in-storage by one. /// /// # Note /// /// A contract whose refcount dropped to zero isn't automatically removed. A `remove_code` /// transaction must be submitted by the original uploader to do so. pub fn decrement_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { >::mutate(code_hash, |existing| { if let Some(info) = existing { info.refcount = info.refcount.saturating_sub(1); } }); Ok(()) } /// Try to remove code together with all associated information. pub fn try_remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { >::try_mutate_exists(&code_hash, |existing| { if let Some(owner_info) = existing { ensure!(owner_info.refcount == 0, >::CodeInUse); ensure!(&owner_info.owner == origin, BadOrigin); T::Currency::unreserve(&owner_info.owner, owner_info.deposit); *existing = None; >::remove(&code_hash); >::remove(&code_hash); >::deposit_event(Event::CodeRemoved { code_hash }); Ok(()) } else { Err(>::CodeNotFound.into()) } }) } /// Load code with the given code hash. /// /// If the module was instrumented with a lower version of schedule than /// the current one given as an argument, then this function will perform /// re-instrumentation and update the cache in the storage. pub fn load( code_hash: CodeHash, schedule: &Schedule, gas_meter: &mut GasMeter, ) -> Result, DispatchError> where T::AccountId: UncheckedFrom + AsRef<[u8]>, { gas_meter.charge(CodeToken::Load(estimate_code_size::, _>(&code_hash)?))?; let mut prefab_module = >::get(code_hash).ok_or_else(|| Error::::CodeNotFound)?; prefab_module.code_hash = code_hash; if prefab_module.instruction_weights_version < schedule.instruction_weights.version { // The instruction weights have changed. // We need to re-instrument the code with the new instruction weights. gas_meter.charge(CodeToken::Reinstrument(estimate_code_size::, _>( &code_hash, )?))?; reinstrument(&mut prefab_module, schedule)?; } Ok(prefab_module) } /// Instruments the passed prefab wasm module with the supplied schedule. pub fn reinstrument( prefab_module: &mut PrefabWasmModule, schedule: &Schedule, ) -> Result<(), DispatchError> { let original_code = >::get(&prefab_module.code_hash).ok_or_else(|| Error::::CodeNotFound)?; prefab_module.code = prepare::reinstrument_contract::(original_code, schedule)?; prefab_module.instruction_weights_version = schedule.instruction_weights.version; >::insert(&prefab_module.code_hash, &*prefab_module); Ok(()) } /// Get the size of the code stored at `code_hash` without loading it. /// /// The returned value is slightly too large when using it for the [`PrefabWasmModule`] /// because it has other fields in addition to the code itself. However, those are negligible /// when compared to the code size. Additionally, charging too much weight is completely safe. fn estimate_code_size(code_hash: &CodeHash) -> Result where T: Config, M: StorageMap, V>, V: codec::FullCodec, { let key = M::hashed_key_for(code_hash); let mut data = [0u8; 0]; let len = sp_io::storage::read(&key, &mut data, 0).ok_or_else(|| Error::::CodeNotFound)?; Ok(len) } /// Costs for operations that are related to code handling. #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Clone, Copy)] enum CodeToken { /// Weight for reinstrumenting a contract contract of the supplied size in bytes. Reinstrument(u32), /// Weight for loading a contract per byte. Load(u32), } impl Token for CodeToken { fn weight(&self) -> Weight { use self::CodeToken::*; // In case of `Load` we already covered the general costs of // calling the storage but still need to account for the actual size of the // contract code. This is why we substract `T::*::(0)`. We need to do this at this // point because when charging the general weight for calling the contract we not know the // size of the contract. match *self { Reinstrument(len) => T::WeightInfo::reinstrument(len / 1024), Load(len) => T::WeightInfo::call_with_code_kb(len / 1024) .saturating_sub(T::WeightInfo::call_with_code_kb(0)), } } }