mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 02:51:08 +00:00
contracts: switch to wasmi gas metering (#14084)
* upgrade to wasmi 0.29 * prepare cleanup * sync ref_time w engine from the stack frame * proc_macro: sync gas in host funcs save: compiles, only gas pushing left to macro WIP proc macro proc macro: done * clean benchmarks & schedule: w_base = w_i64const * scale gas values btw engine and gas meter * (re)instrumentation & code_cache removed * remove gas() host fn, continue clean-up save * address review comments * move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob * refactor: no reftime_limit&schedule passes, no CodeStorage * bugs fixing * fix tests: expected deposit amount * fix prepare::tests * update tests and fix bugs tests::run_out_of_gas_engine, need 2 more save: 2 bugs with gas syncs: 1 of 2 tests done gas_syncs_no_overcharge bug fixed, test passes! cleaned out debug prints second bug is not a bug disabled_chain_extension test fix (err msg) tests run_out_of_fuel_host, chain_extension pass all tests pass * update docs * bump wasmi 0.30.0 * benchmarks updated, tests pass * refactoring * s/OwnerInfo/CodeInfo/g; * migration: draft, compiles * migration: draft, runs * migration: draft, runs (fixing) * deposits repaid non pro rata * deposits repaid pro rata * better try-runtime output * even better try-runtime output * benchmark migration * fix merge leftover * add forgotten fixtures, fix docs * address review comments * ci fixes * cleanup * benchmarks::prepare to return DispatchError * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * store memory limits to CodeInfo * ci: roll back weights * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * drive-by: update Readme and pallet rustdoc * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.29 * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.30 again * query memory limits from wasmi * better migration types * ci: pull weights from master * refactoring * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * addressing review comments * refactor * address review comments * optimize migration * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * another review round comments addressed * ci fix one * clippy fix * ci fix two --------- Co-authored-by: command-bot <>
This commit is contained in:
@@ -179,7 +179,11 @@ impl<T: Config> MigrationStep for Migration<T> {
|
||||
})
|
||||
// If it fails we fallback to minting the ED.
|
||||
.unwrap_or_else(|err| {
|
||||
log::error!(target: LOG_TARGET, "Failed to transfer the base deposit, reason: {:?}", err);
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to transfer the base deposit, reason: {:?}",
|
||||
err
|
||||
);
|
||||
T::Currency::deposit_creating(&deposit_account, min_balance);
|
||||
min_balance
|
||||
});
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
|
||||
//! repay deposits.
|
||||
|
||||
use crate::{
|
||||
migration::{IsFinished, MigrationStep},
|
||||
weights::WeightInfo,
|
||||
AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
codec, pallet_prelude::*, storage_alias, traits::ReservableCurrency, DefaultNoBound, Identity,
|
||||
};
|
||||
use scale_info::prelude::format;
|
||||
use sp_core::hexdisplay::HexDisplay;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_runtime::TryRuntimeError;
|
||||
use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
mod old {
|
||||
use super::*;
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct OwnerInfo<T: Config> {
|
||||
pub owner: AccountIdOf<T>,
|
||||
#[codec(compact)]
|
||||
pub deposit: BalanceOf<T>,
|
||||
#[codec(compact)]
|
||||
pub refcount: u64,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct PrefabWasmModule {
|
||||
#[codec(compact)]
|
||||
pub instruction_weights_version: u32,
|
||||
#[codec(compact)]
|
||||
pub initial: u32,
|
||||
#[codec(compact)]
|
||||
pub maximum: u32,
|
||||
pub code: Vec<u8>,
|
||||
pub determinism: Determinism,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type OwnerInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T>>;
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeStorage<T: Config> =
|
||||
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct CodeInfo<T: Config> {
|
||||
owner: AccountIdOf<T>,
|
||||
#[codec(compact)]
|
||||
deposit: BalanceOf<T>,
|
||||
#[codec(compact)]
|
||||
refcount: u64,
|
||||
determinism: Determinism,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub type CodeInfoOf<T: Config> = StorageMap<Pallet<T>, Twox64Concat, CodeHash<T>, CodeInfo<T>>;
|
||||
|
||||
#[storage_alias]
|
||||
pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_old_dummy_code<T: Config>(len: usize, account: T::AccountId) {
|
||||
use sp_runtime::traits::Hash;
|
||||
|
||||
let code = vec![42u8; len];
|
||||
let hash = T::Hashing::hash(&code);
|
||||
PristineCode::<T>::insert(hash, code.clone());
|
||||
|
||||
let module = old::PrefabWasmModule {
|
||||
instruction_weights_version: Default::default(),
|
||||
initial: Default::default(),
|
||||
maximum: Default::default(),
|
||||
code,
|
||||
determinism: Determinism::Enforced,
|
||||
};
|
||||
old::CodeStorage::<T>::insert(hash, module);
|
||||
|
||||
let info = old::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX };
|
||||
old::OwnerInfoOf::<T>::insert(hash, info);
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
|
||||
pub struct Migration<T: Config> {
|
||||
last_code_hash: Option<CodeHash<T>>,
|
||||
}
|
||||
|
||||
impl<T: Config> MigrationStep for Migration<T> {
|
||||
const VERSION: u16 = 12;
|
||||
|
||||
fn max_step_weight() -> Weight {
|
||||
T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
|
||||
}
|
||||
|
||||
fn step(&mut self) -> (IsFinished, Weight) {
|
||||
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
|
||||
old::OwnerInfoOf::<T>::iter_from(old::OwnerInfoOf::<T>::hashed_key_for(last_key))
|
||||
} else {
|
||||
old::OwnerInfoOf::<T>::iter()
|
||||
};
|
||||
if let Some((hash, old_info)) = iter.next() {
|
||||
log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash);
|
||||
|
||||
let module = old::CodeStorage::<T>::take(hash)
|
||||
.expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
|
||||
|
||||
let code_len = module.code.len();
|
||||
// We print this to measure the impact of the migration.
|
||||
// Storage removed: deleted PrefabWasmModule's encoded len.
|
||||
// Storage added: determinism field encoded len (as all other CodeInfo fields are the
|
||||
// same as in the deleted OwnerInfo).
|
||||
log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
|
||||
|
||||
// Storage usage prices could change over time, and accounts who uploaded their
|
||||
// contracts code before the storage deposits where introduced, had not been ever
|
||||
// charged with any deposit for that (see migration v6).
|
||||
//
|
||||
// This is why deposit to be refunded here is calculated as follows:
|
||||
//
|
||||
// 1. Calculate the deposit amount for storage before the migration, given current
|
||||
// prices.
|
||||
// 2. Given current reserved deposit amount, calculate the correction factor.
|
||||
// 3. Calculate the deposit amount for storage after the migration, given current
|
||||
// prices.
|
||||
// 4. Calculate real deposit amount to be reserved after the migration.
|
||||
let price_per_byte = T::DepositPerByte::get();
|
||||
let price_per_item = T::DepositPerItem::get();
|
||||
let bytes_before = module
|
||||
.encoded_size()
|
||||
.saturating_add(code_len)
|
||||
.saturating_add(old::OwnerInfo::<T>::max_encoded_len()) as u32;
|
||||
let items_before = 3u32;
|
||||
let deposit_expected_before = price_per_byte
|
||||
.saturating_mul(bytes_before.into())
|
||||
.saturating_add(price_per_item.saturating_mul(items_before.into()));
|
||||
let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
|
||||
.unwrap_or_default()
|
||||
.min(FixedU128::from_u32(1));
|
||||
let bytes_after = code_len.saturating_add(CodeInfo::<T>::max_encoded_len()) as u32;
|
||||
let items_after = 2u32;
|
||||
let deposit_expected_after = price_per_byte
|
||||
.saturating_mul(bytes_after.into())
|
||||
.saturating_add(price_per_item.saturating_mul(items_after.into()));
|
||||
let deposit = ratio.saturating_mul_int(deposit_expected_after);
|
||||
|
||||
let info = CodeInfo::<T> {
|
||||
determinism: module.determinism,
|
||||
owner: old_info.owner,
|
||||
deposit,
|
||||
refcount: old_info.refcount,
|
||||
};
|
||||
|
||||
let amount = old_info.deposit.saturating_sub(info.deposit);
|
||||
if !amount.is_zero() {
|
||||
T::Currency::unreserve(&info.owner, amount);
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Deposit refunded: {:?} Balance, to: {:?}",
|
||||
&amount,
|
||||
HexDisplay::from(&info.owner.encode())
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"new deposit: {:?} >= old deposit: {:?}",
|
||||
&info.deposit,
|
||||
&old_info.deposit
|
||||
);
|
||||
}
|
||||
CodeInfoOf::<T>::insert(hash, info);
|
||||
|
||||
self.last_code_hash = Some(hash);
|
||||
|
||||
(IsFinished::No, T::WeightInfo::v12_migration_step(code_len as u32))
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
|
||||
(IsFinished::Yes, T::WeightInfo::v12_migration_step(0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let len = 100;
|
||||
log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
|
||||
let sample: Vec<_> = old::OwnerInfoOf::<T>::iter()
|
||||
.take(len)
|
||||
.map(|(k, v)| {
|
||||
let module = old::CodeStorage::<T>::get(k)
|
||||
.expect("No PrefabWasmModule found for code_hash: {:?}");
|
||||
let info: CodeInfo<T> = CodeInfo {
|
||||
determinism: module.determinism,
|
||||
deposit: v.deposit,
|
||||
refcount: v.refcount,
|
||||
owner: v.owner,
|
||||
};
|
||||
(k, info)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let storage: u32 =
|
||||
old::CodeStorage::<T>::iter().map(|(_k, v)| v.encoded_size() as u32).sum();
|
||||
let mut deposit: BalanceOf<T> = Default::default();
|
||||
old::OwnerInfoOf::<T>::iter().for_each(|(_k, v)| deposit += v.deposit);
|
||||
|
||||
Ok((sample, deposit, storage).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let state = <(Vec<(CodeHash<T>, CodeInfo<T>)>, BalanceOf<T>, u32) as Decode>::decode(
|
||||
&mut &state[..],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len());
|
||||
for (hash, old) in state.0 {
|
||||
let info = CodeInfoOf::<T>::get(&hash)
|
||||
.expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
|
||||
ensure!(info.determinism == old.determinism, "invalid determinism");
|
||||
ensure!(info.owner == old.owner, "invalid owner");
|
||||
ensure!(info.refcount == old.refcount, "invalid refcount");
|
||||
}
|
||||
|
||||
if let Some((k, _)) = old::CodeStorage::<T>::iter().next() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"CodeStorage is still NOT empty, found code_hash: {:?}",
|
||||
k
|
||||
);
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
|
||||
}
|
||||
if let Some((k, _)) = old::OwnerInfoOf::<T>::iter().next() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"OwnerInfoOf is still NOT empty, found code_hash: {:?}",
|
||||
k
|
||||
);
|
||||
} else {
|
||||
log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
|
||||
}
|
||||
|
||||
let mut deposit: BalanceOf<T> = Default::default();
|
||||
let mut items = 0u32;
|
||||
let mut storage_info = 0u32;
|
||||
CodeInfoOf::<T>::iter().for_each(|(_k, v)| {
|
||||
deposit += v.deposit;
|
||||
items += 1;
|
||||
storage_info += v.encoded_size() as u32;
|
||||
});
|
||||
let mut storage_code = 0u32;
|
||||
PristineCode::<T>::iter().for_each(|(_k, v)| {
|
||||
storage_code += v.len() as u32;
|
||||
});
|
||||
let (_, old_deposit, storage_module) = state;
|
||||
// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
|
||||
// I.e. code info adds up 1 byte per record.
|
||||
let info_bytes_added = items.clone();
|
||||
// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
|
||||
let storage_removed = storage_module.saturating_sub(info_bytes_added);
|
||||
// module+code+info - bytes
|
||||
let storage_was = storage_module
|
||||
.saturating_add(storage_code)
|
||||
.saturating_add(storage_info)
|
||||
.saturating_sub(info_bytes_added);
|
||||
// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
|
||||
// items per code).
|
||||
let items_removed = items;
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Storage freed, bytes: {} (of {}), items: {} (of {})",
|
||||
storage_removed,
|
||||
storage_was,
|
||||
items_removed,
|
||||
items_removed * 3,
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Deposits returned, total: {:?} Balance (of {:?} Balance)",
|
||||
old_deposit.saturating_sub(deposit),
|
||||
old_deposit,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user