contracts: Allow indeterministic instructions off-chain (#12469)

* Allow indetermistic instructions off-chain

* Apply suggestions from code review

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

* fmt

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
Alexander Theißen
2022-10-24 19:48:04 +02:00
committed by GitHub
parent d0dcf008ec
commit 3ae4be8662
15 changed files with 926 additions and 169 deletions
+127 -70
View File
@@ -32,65 +32,54 @@ use sp_std::{marker::PhantomData, prelude::*};
pub struct Migration<T: Config>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for Migration<T> {
fn on_runtime_upgrade() -> Weight {
let version = StorageVersion::get::<Pallet<T>>();
let version = <Pallet<T>>::on_chain_storage_version();
let mut weight = Weight::zero();
if version < 4 {
weight = weight.saturating_add(v4::migrate::<T>());
StorageVersion::new(4).put::<Pallet<T>>();
v4::migrate::<T>(&mut weight);
}
if version < 5 {
weight = weight.saturating_add(v5::migrate::<T>());
StorageVersion::new(5).put::<Pallet<T>>();
v5::migrate::<T>(&mut weight);
}
if version < 6 {
weight = weight.saturating_add(v6::migrate::<T>());
StorageVersion::new(6).put::<Pallet<T>>();
v6::migrate::<T>(&mut weight);
}
if version < 7 {
weight = weight.saturating_add(v7::migrate::<T>());
StorageVersion::new(7).put::<Pallet<T>>();
v7::migrate::<T>(&mut weight);
}
if version < 8 {
weight = weight.saturating_add(v8::migrate::<T>());
StorageVersion::new(8).put::<Pallet<T>>();
v8::migrate::<T>(&mut weight);
}
if version < 9 {
v9::migrate::<T>(&mut weight);
}
StorageVersion::new(9).put::<Pallet<T>>();
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
let version = StorageVersion::get::<Pallet<T>>();
let version = <Pallet<T>>::on_chain_storage_version();
if version < 7 {
return Ok(vec![])
}
if version < 8 {
if version == 8 {
v8::pre_upgrade::<T>()?;
}
Ok(vec![])
Ok(version.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), &'static str> {
let version = StorageVersion::get::<Pallet<T>>();
if version < 7 {
return Ok(())
}
if version < 8 {
v8::post_upgrade::<T>()?;
}
Ok(())
fn post_upgrade(state: Vec<u8>) -> Result<(), &'static str> {
let version = Decode::decode(&mut state.as_ref()).map_err(|_| "Cannot decode version")?;
post_checks::post_upgrade::<T>(version)
}
}
@@ -98,10 +87,10 @@ impl<T: Config> OnRuntimeUpgrade for Migration<T> {
mod v4 {
use super::*;
pub fn migrate<T: Config>() -> Weight {
pub fn migrate<T: Config>(weight: &mut Weight) {
#[allow(deprecated)]
migration::remove_storage_prefix(<Pallet<T>>::name().as_bytes(), b"CurrentSchedule", b"");
T::DbWeight::get().writes(1)
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
}
@@ -169,11 +158,9 @@ mod v5 {
#[storage_alias]
type DeletionQueue<T: Config> = StorageValue<Pallet<T>, Vec<DeletedContract>>;
pub fn migrate<T: Config>() -> Weight {
let mut weight = Weight::zero();
pub fn migrate<T: Config>(weight: &mut Weight) {
<ContractInfoOf<T>>::translate(|_key, old: OldContractInfo<T>| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
match old {
OldContractInfo::Alive(old) => Some(ContractInfo::<T> {
trie_id: old.trie_id,
@@ -185,12 +172,10 @@ mod v5 {
});
DeletionQueue::<T>::translate(|old: Option<Vec<OldDeletedContract>>| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect())
})
.ok();
weight
}
}
@@ -214,14 +199,14 @@ mod v6 {
}
#[derive(Encode, Decode)]
struct PrefabWasmModule {
pub struct PrefabWasmModule {
#[codec(compact)]
instruction_weights_version: u32,
pub instruction_weights_version: u32,
#[codec(compact)]
initial: u32,
pub initial: u32,
#[codec(compact)]
maximum: u32,
code: Vec<u8>,
pub maximum: u32,
pub code: Vec<u8>,
}
use v5::ContractInfo as OldContractInfo;
@@ -258,11 +243,9 @@ mod v6 {
#[storage_alias]
type OwnerInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T>>;
pub fn migrate<T: Config>() -> Weight {
let mut weight = Weight::zero();
pub fn migrate<T: Config>(weight: &mut Weight) {
<ContractInfoOf<T>>::translate(|_key, old: OldContractInfo<T>| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
Some(ContractInfo::<T> {
trie_id: old.trie_id,
code_hash: old.code_hash,
@@ -274,7 +257,7 @@ mod v6 {
.expect("Infinite input; no dead input space; qed");
<CodeStorage<T>>::translate(|key, old: OldPrefabWasmModule| {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
<OwnerInfoOf<T>>::insert(
key,
OwnerInfo {
@@ -290,8 +273,6 @@ mod v6 {
code: old.code,
})
});
weight
}
}
@@ -299,14 +280,14 @@ mod v6 {
mod v7 {
use super::*;
pub fn migrate<T: Config>() -> Weight {
pub fn migrate<T: Config>(weight: &mut Weight) {
#[storage_alias]
type AccountCounter<T: Config> = StorageValue<Pallet<T>, u64, ValueQuery>;
#[storage_alias]
type Nonce<T: Config> = StorageValue<Pallet<T>, u64, ValueQuery>;
Nonce::<T>::set(AccountCounter::<T>::take());
T::DbWeight::get().reads_writes(1, 2)
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2))
}
}
@@ -317,23 +298,21 @@ mod v8 {
use v6::ContractInfo as OldContractInfo;
#[derive(Encode, Decode)]
struct ContractInfo<T: Config> {
trie_id: TrieId,
code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
storage_byte_deposit: BalanceOf<T>,
storage_item_deposit: BalanceOf<T>,
storage_base_deposit: BalanceOf<T>,
pub struct ContractInfo<T: Config> {
pub trie_id: TrieId,
pub code_hash: CodeHash<T>,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
pub storage_item_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
}
#[storage_alias]
type ContractInfoOf<T: Config, V> =
StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, V>;
pub fn migrate<T: Config>() -> Weight {
let mut weight = Weight::zero();
pub fn migrate<T: Config>(weight: &mut Weight) {
<ContractInfoOf<T, ContractInfo<T>>>::translate_values(|old: OldContractInfo<T>| {
// Count storage items of this contract
let mut storage_bytes = 0u32;
@@ -359,8 +338,9 @@ mod v8 {
// Reads: One read for each storage item plus the contract info itself.
// Writes: Only the new contract info.
weight = weight
.saturating_add(T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1));
weight.saturating_accrue(
T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1),
);
Some(ContractInfo {
trie_id: old.trie_id,
@@ -372,8 +352,6 @@ mod v8 {
storage_base_deposit,
})
});
weight
}
#[cfg(feature = "try-runtime")]
@@ -385,9 +363,78 @@ mod v8 {
}
Ok(())
}
}
#[cfg(feature = "try-runtime")]
pub fn post_upgrade<T: Config>() -> Result<(), &'static str> {
/// Update `CodeStorage` with the new `determinism` field.
mod v9 {
use super::*;
use crate::Determinism;
use v6::PrefabWasmModule as OldPrefabWasmModule;
#[derive(Encode, Decode)]
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]
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
pub fn migrate<T: Config>(weight: &mut Weight) {
<CodeStorage<T>>::translate_values(|old: OldPrefabWasmModule| {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
Some(PrefabWasmModule {
instruction_weights_version: old.instruction_weights_version,
initial: old.initial,
maximum: old.maximum,
code: old.code,
determinism: Determinism::Deterministic,
})
});
}
}
// Post checks always need to be run against the latest storage version. This is why we
// do not scope them in the per version modules. They always need to be ported to the latest
// version.
#[cfg(feature = "try-runtime")]
mod post_checks {
use super::*;
use crate::Determinism;
use sp_io::default_child_storage as child;
use v8::ContractInfo;
use v9::PrefabWasmModule;
#[storage_alias]
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
#[storage_alias]
type ContractInfoOf<T: Config, V> =
StorageMap<Pallet<T>, Twox64Concat, <T as frame_system::Config>::AccountId, V>;
pub fn post_upgrade<T: Config>(old_version: StorageVersion) -> Result<(), &'static str> {
if old_version < 7 {
return Ok(())
}
if old_version < 8 {
v8::<T>()?;
}
if old_version < 9 {
v9::<T>()?;
}
Ok(())
}
fn v8<T: Config>() -> Result<(), &'static str> {
use frame_support::traits::ReservableCurrency;
for (key, value) in ContractInfoOf::<T, ContractInfo<T>>::iter() {
let reserved = T::Currency::reserved_balance(&key);
@@ -413,4 +460,14 @@ mod v8 {
}
Ok(())
}
fn v9<T: Config>() -> Result<(), &'static str> {
for value in CodeStorage::<T>::iter_values() {
ensure!(
value.determinism == Determinism::Deterministic,
"All pre-existing codes need to be deterministic."
);
}
Ok(())
}
}