contracts: Multi block migrations (#14045)

* Frame Add translate_next

This works similarly to to `translate` but only translate a single entry.
This function will be useful in the context of multi-block migration.

* Move to lazy migration

* Updates

* simplify MockMigration

* wip

* wip

* add bench

* add bench

* fmt

* fix bench

* add .

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Scalfold v10 / v11 fix tests

* PR comment

* tweak pub use

* wip

* wip

* wip

* misc merge master

* misc merge master

* wip

* rm tmp stuff

* wip

* wip

* wip

* wip

* wip

* fixes

* add state

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix

* fixed compilation

* clean up logs

* wip

* Revert "Frame Add translate_next"

This reverts commit 10318fc95c42b1f7f25efeb35e6d947ea02bed88.

* Fix v10 logic

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* wip

* fixes

* exercise del_queue

* bump sample size

* fmt

* wip

* blank line

* fix lint

* fix rustdoc job lint

* PR comment do not use dangerous into()

* Ad macros for updating mod visibility

* Add doc

* Add max_weight to integrity_test

* fix compilation

* Add no migration tests

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* fix clippy

* PR review

* Update frame/contracts/src/lib.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Fix master merge

* fix merge 2

* fix tryruntime

* fix lint

---------

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: command-bot <>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
PG Herveou
2023-05-31 16:19:31 +02:00
committed by GitHub
parent b43a1b0b55
commit c7c5fc709c
10 changed files with 2841 additions and 1482 deletions
+89 -6
View File
@@ -107,7 +107,10 @@ use crate::{
use codec::{Codec, Decode, Encode, HasCompact};
use environmental::*;
use frame_support::{
dispatch::{DispatchError, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin},
dispatch::{
DispatchError, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin,
WithPostDispatchInfo,
},
ensure,
error::BadOrigin,
traits::{
@@ -120,18 +123,18 @@ use frame_support::{
use frame_system::{ensure_signed, pallet_prelude::OriginFor, EventRecord, Pallet as System};
use pallet_contracts_primitives::{
Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult,
ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue,
StorageDeposit,
ContractInstantiateResult, ContractResult, ExecReturnValue, GetStorageResult,
InstantiateReturnValue, StorageDeposit,
};
use scale_info::TypeInfo;
use smallvec::Array;
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup, Zero};
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
pub use crate::{
address::{AddressGenerator, DefaultAddressGenerator},
exec::Frame,
migration::Migration,
migration::{MigrateSequence, Migration, NoopMigration},
pallet::*,
schedule::{HostFnWeights, InstructionWeights, Limits, Schedule},
wasm::Determinism,
@@ -179,7 +182,12 @@ pub mod pallet {
use frame_system::pallet_prelude::*;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(9);
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
/// Hard coded storage version for running tests that depend on the current storage version.
#[cfg(any(test, feature = "runtime-benchmarks"))]
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
@@ -316,11 +324,22 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight {
use migration::MigrateResult::*;
let (result, weight) = Migration::<T>::migrate(remaining_weight);
let remaining_weight = remaining_weight.saturating_sub(weight);
if !matches!(result, Completed | NoMigrationInProgress) {
return weight
}
ContractInfo::<T>::process_deletion_queue_batch(remaining_weight)
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
}
fn integrity_test() {
Migration::<T>::integrity_test();
// Total runtime memory limit
let max_runtime_mem: u32 = T::Schedule::get().limits.runtime_memory;
// Memory limits for a single contract:
@@ -499,6 +518,7 @@ pub mod pallet {
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
determinism: Determinism,
) -> DispatchResult {
Migration::<T>::ensure_migrated()?;
let origin = ensure_signed(origin)?;
Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism)
.map(|_| ())
@@ -514,6 +534,7 @@ pub mod pallet {
origin: OriginFor<T>,
code_hash: CodeHash<T>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let origin = ensure_signed(origin)?;
<PrefabWasmModule<T>>::remove(&origin, code_hash)?;
// we waive the fee because removing unused code is beneficial
@@ -537,6 +558,7 @@ pub mod pallet {
dest: AccountIdLookupOf<T>,
code_hash: CodeHash<T>,
) -> DispatchResult {
Migration::<T>::ensure_migrated()?;
ensure_root(origin)?;
let dest = T::Lookup::lookup(dest)?;
<ContractInfoOf<T>>::try_mutate(&dest, |contract| {
@@ -586,6 +608,7 @@ pub mod pallet {
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let common = CommonInput {
origin: Origin::from_runtime_origin(origin)?,
value,
@@ -645,6 +668,7 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let code_len = code.len() as u32;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
@@ -687,6 +711,7 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
@@ -709,6 +734,33 @@ pub mod pallet {
T::WeightInfo::instantiate(data_len, salt_len),
)
}
/// When a migration is in progress, this dispatchable can be used to run migration steps.
/// Calls that contribute to advancing the migration have their fees waived, as it's helpful
/// for the chain. Note that while the migration is in progress, the pallet will also
/// leverage the `on_idle` hooks to run migration steps.
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))]
pub fn migrate(origin: OriginFor<T>, weight_limit: Weight) -> DispatchResultWithPostInfo {
use migration::MigrateResult::*;
ensure_signed(origin)?;
let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate());
let (result, weight) = Migration::<T>::migrate(weight_limit);
match result {
Completed =>
Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::No }),
InProgress { steps_done, .. } if steps_done > 0 =>
Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::No }),
InProgress { .. } =>
Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::Yes }),
NoMigrationInProgress | NoMigrationPerformed => {
let err: DispatchError = <Error<T>>::NoMigrationPerformed.into();
Err(err.with_weight(T::WeightInfo::migrate()))
},
}
}
}
#[pallet::event]
@@ -861,6 +913,10 @@ pub mod pallet {
CodeRejected,
/// An indetermistic code was used in a context where this is not permitted.
Indeterministic,
/// A pending migration needs to complete before the extrinsic can be called.
MigrationInProgress,
/// Migrate dispatch call was attempted but no migration was performed.
NoMigrationPerformed,
}
/// A mapping from an original code hash to the original code, untouched by instrumentation.
@@ -920,6 +976,10 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type DeletionQueueCounter<T: Config> =
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
#[pallet::storage]
pub(crate) type MigrationInProgress<T: Config> =
StorageValue<_, migration::Cursor, OptionQuery>;
}
/// The type of origins supported by the contracts pallet.
@@ -1210,6 +1270,21 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
}
}
macro_rules! ensure_no_migration_in_progress {
() => {
if Migration::<T>::in_progress() {
return ContractResult {
gas_consumed: Zero::zero(),
gas_required: Zero::zero(),
storage_deposit: Default::default(),
debug_message: Vec::new(),
result: Err(Error::<T>::MigrationInProgress.into()),
events: None,
}
}
};
}
impl<T: Config> Pallet<T> {
/// Perform a call to a specified contract.
///
@@ -1234,6 +1309,8 @@ impl<T: Config> Pallet<T> {
collect_events: CollectEvents,
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>> {
ensure_no_migration_in_progress!();
let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) {
Some(DebugBufferVec::<T>::default())
} else {
@@ -1290,6 +1367,8 @@ impl<T: Config> Pallet<T> {
debug: DebugInfo,
collect_events: CollectEvents,
) -> ContractInstantiateResult<T::AccountId, BalanceOf<T>, EventRecordOf<T>> {
ensure_no_migration_in_progress!();
let mut debug_message = if debug == DebugInfo::UnsafeDebug {
Some(DebugBufferVec::<T>::default())
} else {
@@ -1333,6 +1412,7 @@ impl<T: Config> Pallet<T> {
storage_deposit_limit: Option<BalanceOf<T>>,
determinism: Determinism,
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
Migration::<T>::ensure_migrated()?;
let schedule = T::Schedule::get();
let module = PrefabWasmModule::from_code(
code,
@@ -1353,6 +1433,9 @@ impl<T: Config> Pallet<T> {
/// Query storage of a specified contract under a specified key.
pub fn get_storage(address: T::AccountId, key: Vec<u8>) -> GetStorageResult {
if Migration::<T>::in_progress() {
return Err(ContractAccessError::MigrationInProgress)
}
let contract_info =
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;