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
+20 -10
View File
@@ -22,7 +22,7 @@
use crate::{
chain_extension::ChainExtension,
storage::meter::Diff,
wasm::{env_def::ImportSatisfyCheck, OwnerInfo, PrefabWasmModule},
wasm::{env_def::ImportSatisfyCheck, Determinism, OwnerInfo, PrefabWasmModule},
AccountIdOf, CodeVec, Config, Error, Schedule,
};
use codec::{Encode, MaxEncodedLen};
@@ -182,8 +182,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
Ok(())
}
fn inject_gas_metering(self) -> Result<Self, &'static str> {
let gas_rules = self.schedule.rules(&self.module);
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
let gas_rules = self.schedule.rules(&self.module, determinism);
let contract_module =
wasm_instrument::gas_metering::inject(self.module, &gas_rules, "seal0")
.map_err(|_| "gas instrumentation failed")?;
@@ -369,6 +369,7 @@ fn get_memory_limits<T: Config>(
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
original_code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
let result = (|| {
let contract_module = ContractModule::new(original_code, schedule)?;
@@ -376,17 +377,20 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
contract_module.ensure_no_internal_memory()?;
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
contract_module.ensure_no_floating_types()?;
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
if matches!(determinism, Determinism::Deterministic) {
contract_module.ensure_no_floating_types()?;
}
// We disallow importing `gas` function here since it is treated as implementation detail.
let disallowed_imports = [b"gas".as_ref()];
let memory_limits =
get_memory_limits(contract_module.scan_imports::<C>(&disallowed_imports)?, schedule)?;
let code = contract_module
.inject_gas_metering()?
.inject_gas_metering(determinism)?
.inject_stack_height_metering()?
.into_wasm_code()?;
@@ -404,9 +408,11 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
original_code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
let (code, (initial, maximum)) = check_and_instrument::<C, T>(original_code.as_ref(), schedule)
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
let (code, (initial, maximum)) =
check_and_instrument::<C, T>(original_code.as_ref(), schedule, determinism)
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
let original_code_len = original_code.len();
let mut module = PrefabWasmModule {
@@ -414,6 +420,7 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
initial,
maximum,
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
determinism,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code),
owner_info: None,
@@ -449,8 +456,9 @@ pub fn prepare_contract<T: Config>(
original_code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner)
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner, determinism)
}
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
@@ -461,8 +469,9 @@ pub fn prepare_contract<T: Config>(
pub fn reinstrument_contract<T: Config>(
original_code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<Vec<u8>, &'static str> {
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule)?.0)
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule, determinism)?.0)
}
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
@@ -495,6 +504,7 @@ pub mod benchmarking {
maximum: memory_limits.1,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
determinism: Determinism::Deterministic,
code: contract_module
.into_wasm_code()?
.try_into()
@@ -572,7 +582,7 @@ mod tests {
},
.. Default::default()
};
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE);
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE, Determinism::Deterministic);
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
}
};