Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

contracts: Multi block migrations #14045

Merged
merged 83 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
b878662
Frame Add translate_next
pgherveou Apr 28, 2023
7ab5774
Move to lazy migration
athei Feb 22, 2023
e49f0ce
Updates
pgherveou May 2, 2023
758902a
simplify MockMigration
pgherveou May 2, 2023
3a9c925
wip
pgherveou May 2, 2023
c7aa478
wip
pgherveou May 2, 2023
c5d33ce
add bench
pgherveou May 2, 2023
a27b8bc
add bench
pgherveou May 2, 2023
d4eee87
fmt
pgherveou May 2, 2023
f5d9638
fix bench
pgherveou May 2, 2023
dc501ce
add .
pgherveou May 2, 2023
e6cd691
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts
May 2, 2023
5db9beb
Apply suggestions from code review
pgherveou May 5, 2023
1bd551e
Scalfold v10 / v11 fix tests
pgherveou May 8, 2023
d718995
PR comment
pgherveou May 8, 2023
131cc45
tweak pub use
pgherveou May 8, 2023
1f22230
wip
pgherveou May 8, 2023
f3f69c8
wip
pgherveou May 9, 2023
1b07693
wip
pgherveou May 9, 2023
d8500b7
Merge branch 'master' into at/migration
pgherveou May 9, 2023
e561a69
misc merge master
pgherveou May 9, 2023
2a57bff
Merge branch 'at/migration' into pg/migration
pgherveou May 9, 2023
4e7a1f7
misc merge master
pgherveou May 9, 2023
b1dd6a1
Merge branch 'at/migration' into pg/migration
pgherveou May 9, 2023
db7b4e9
wip
pgherveou May 9, 2023
3c08a37
rm tmp stuff
pgherveou May 9, 2023
e966c87
wip
pgherveou May 9, 2023
f1134c6
wip
pgherveou May 10, 2023
a85ff26
wip
pgherveou May 10, 2023
87a8f3b
wip
pgherveou May 10, 2023
193c788
wip
pgherveou May 10, 2023
aafaa03
fixes
pgherveou May 10, 2023
0d84f28
add state
pgherveou May 10, 2023
be9bc81
wip
pgherveou May 10, 2023
59bd209
wip
pgherveou May 10, 2023
92ddfa0
wip
pgherveou May 10, 2023
1221345
wip
pgherveou May 10, 2023
8445ae7
wip
pgherveou May 10, 2023
42e5b5b
wip
pgherveou May 11, 2023
82ed2d1
wip
pgherveou May 11, 2023
dee4889
wip
pgherveou May 11, 2023
6aff1bd
wip
pgherveou May 11, 2023
4a7ec2e
wip
pgherveou May 11, 2023
f21b524
wip
pgherveou May 11, 2023
42cf92c
wip
pgherveou May 11, 2023
68ff246
wip
pgherveou May 11, 2023
bcd404b
wip
pgherveou May 11, 2023
0a321db
fix
pgherveou May 11, 2023
4b771b2
fixed compilation
pgherveou May 11, 2023
7864d6c
clean up logs
pgherveou May 11, 2023
2af1523
wip
pgherveou May 11, 2023
4c4758b
Revert "Frame Add translate_next"
pgherveou May 11, 2023
7f9b013
Merge branch 'master' into pg/migration
pgherveou May 11, 2023
1194ad3
Fix v10 logic
pgherveou May 12, 2023
657ac6e
Apply suggestions from code review
pgherveou May 12, 2023
69734ae
wip
pgherveou May 12, 2023
98f5df5
fixes
pgherveou May 12, 2023
14b7102
exercise del_queue
pgherveou May 12, 2023
59acab8
bump sample size
pgherveou May 12, 2023
4a142bc
fmt
pgherveou May 12, 2023
48cf78a
wip
pgherveou May 12, 2023
14f078c
blank line
pgherveou May 12, 2023
79b36b9
fix lint
pgherveou May 12, 2023
d954437
fix rustdoc job lint
pgherveou May 15, 2023
9782bd9
PR comment do not use dangerous into()
pgherveou May 15, 2023
82adfd8
Ad macros for updating mod visibility
pgherveou May 15, 2023
25b2ec6
Add doc
pgherveou May 15, 2023
453489f
Add max_weight to integrity_test
pgherveou May 15, 2023
1ceb5e0
fix compilation
pgherveou May 15, 2023
883f54f
Merge branch 'master' into pg/migration
pgherveou May 16, 2023
ab44573
Add no migration tests
pgherveou May 16, 2023
c87e1d7
Merge branch 'master' of https://github.com/paritytech/substrate into…
May 16, 2023
b8ee019
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts
May 16, 2023
2081eae
fix clippy
pgherveou May 16, 2023
aaac768
PR review
pgherveou May 16, 2023
c2e587d
Update frame/contracts/src/lib.rs
pgherveou May 16, 2023
0b690e9
Merge branch 'master' into pg/migration
pgherveou May 26, 2023
1fc4035
Fix master merge
pgherveou May 26, 2023
c6ea2eb
Merge branch 'master' into pg/migration
pgherveou May 26, 2023
0b9c16c
fix merge 2
pgherveou May 26, 2023
25ada0f
fix tryruntime
pgherveou May 28, 2023
7d5e934
fix lint
pgherveou May 29, 2023
de10f33
Merge branch 'master' into pg/migration
pgherveou May 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frame/contracts/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ pub enum ContractAccessError {
DoesntExist,
/// Storage key cannot be decoded from the provided input data.
KeyDecodingFailed,
/// Storage is migrating. Try again later.
MigrationInProgress,
}

bitflags! {
Expand Down
92 changes: 90 additions & 2 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

mod code;
mod sandbox;

use self::{
code::{
body::{self, DynInstr::*},
Expand All @@ -31,12 +30,13 @@ use self::{
};
use crate::{
exec::{AccountIdOf, Key},
migration::{v10, v11, v9, Migrate},
wasm::CallFlags,
Pallet as Contracts, *,
};
use codec::{Encode, MaxEncodedLen};
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller};
use frame_support::weights::Weight;
use frame_support::{pallet_prelude::StorageVersion, weights::Weight};
use frame_system::RawOrigin;
use sp_runtime::{
traits::{Bounded, Hash},
Expand Down Expand Up @@ -234,6 +234,94 @@ benchmarks! {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
}

// This benchmarks the v9 migration step. (update codeStorage)
#[pov_mode = Measured]
v9_migration_step {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
v9::store_old_dummy_code::<T>(c as usize);
let mut m = v9::Migration::<T>::default();
}: {
m.step();
}

// This benchmarks the v10 migration step. (use dedicated deposit_account)
#[pov_mode = Measured]
v10_migration_step {
let contract = <Contract<T>>::with_caller(
whitelisted_caller(), WasmModule::dummy(), vec![],
)?;

v10::store_old_contrat_info::<T>(contract.account_id.clone(), contract.info()?);
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
let mut m = v10::Migration::<T>::default();
}: {
m.step();
}

// This benchmarks the v11 migration step.
#[pov_mode = Measured]
v11_migration_step {
let k in 0 .. 1024;
v11::fill_old_queue::<T>(k as usize);
let mut m = v11::Migration::<T>::default();
}: {
m.step();
}

// This benchmarks the weight of executing Migration::migrate to execute a noop migration.
#[pov_mode = Measured]
migration_noop {
assert_eq!(StorageVersion::get::<Pallet<T>>(), 2);
}: {
Migration::<T>::migrate(Weight::MAX)
} verify {
assert_eq!(StorageVersion::get::<Pallet<T>>(), 2);
}

// This benchmarks the weight of executing Migration::migrate when there are no migration in progress.
#[pov_mode = Measured]
migrate {
StorageVersion::new(0).put::<Pallet<T>>();
<Migration::<T> as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();
let origin: RawOrigin<<T as frame_system::Config>::AccountId> = RawOrigin::Signed(whitelisted_caller());
}: {
<Contracts<T>>::migrate(origin.into(), Weight::MAX).unwrap()
} verify {
pgherveou marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(StorageVersion::get::<Pallet<T>>(), 1);
}

// This benchmarks the weight of running on_runtime_upgrade when there are no migration in progress.
#[pov_mode = Measured]
on_runtime_upgrade_noop {
assert_eq!(StorageVersion::get::<Pallet<T>>(), 2);
}: {
<Migration::<T> as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade()
} verify {
assert!(MigrationInProgress::<T>::get().is_none());
}

// This benchmarks the weight of running on_runtime_upgrade when there is a migration in progress.
#[pov_mode = Measured]
on_runtime_upgrade_in_progress {
StorageVersion::new(0).put::<Pallet<T>>();
let v = vec![42u8].try_into().ok();
MigrationInProgress::<T>::set(v.clone());
}: {
<Migration::<T> as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade()
} verify {
assert!(MigrationInProgress::<T>::get().is_some());
assert_eq!(MigrationInProgress::<T>::get(), v);
}

// This benchmarks the weight of running on_runtime_upgrade when there is a migration to process.
#[pov_mode = Measured]
on_runtime_upgrade {
StorageVersion::new(0).put::<Pallet<T>>();
}: {
<Migration::<T> as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade()
} verify {
assert!(MigrationInProgress::<T>::get().is_some());
}

// This benchmarks the overhead of loading a code of size `c` byte from storage and into
// the sandbox. This does **not** include the actual execution for which the gas meter
// is responsible. This is achieved by generating all code to the `deploy` function
Expand Down
95 changes: 89 additions & 6 deletions frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -499,6 +518,7 @@ pub mod pallet {
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
determinism: Determinism,
) -> DispatchResult {
Migration::<T>::ensure_migrated()?;
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
let origin = ensure_signed(origin)?;
Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism)
.map(|_| ())
Expand All @@ -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
Expand All @@ -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| {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -709,6 +734,33 @@ pub mod pallet {
T::WeightInfo::instantiate(data_len, salt_len),
)
}

pgherveou marked this conversation as resolved.
Show resolved Hide resolved
/// 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)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note from talk with Sasha: should we give a different DispatchClass to this call?

#[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 }),
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
NoMigrationInProgress | NoMigrationPerformed => {
let err: DispatchError = <Error<T>>::NoMigrationPerformed.into();
Err(err.with_weight(T::WeightInfo::migrate()))
},
}
}
}

#[pallet::event]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -920,6 +976,10 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type DeletionQueueCounter<T: Config> =
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;

pgherveou marked this conversation as resolved.
Show resolved Hide resolved
#[pallet::storage]
pub(crate) type MigrationInProgress<T: Config> =
StorageValue<_, migration::Cursor, OptionQuery>;
}

/// The type of origins supported by the contracts pallet.
Expand Down Expand Up @@ -1210,6 +1270,21 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
}
}

macro_rules! ensure_no_migration_in_progress {
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
() => {
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.
///
Expand All @@ -1234,6 +1309,8 @@ impl<T: Config> Pallet<T> {
collect_events: CollectEvents,
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>> {
ensure_no_migration_in_progress!();
agryaznov marked this conversation as resolved.
Show resolved Hide resolved

let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) {
Some(DebugBufferVec::<T>::default())
} else {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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)?;

Expand Down
Loading