mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 10:47:55 +00:00
[contracts] Implement transparent hashing for contract storage (#11501)
* save * builds and old tests pass save: temporary value dropped while borrowed save: finally builds test updated but still fails * type names enhanced * VarSizedKey bounded to new Config param * improved wasm runtime updated funcs * unstable-interface tests fixed * benchmarks fixed * Apply suggestions from code review Co-authored-by: Alexander Theißen <alex.theissen@me.com> * fixes on feedback * fixes on feedback applied + make it build * benchmarks build but fail (old) * "Original code too large" * seal_clear_storage bench fixed (code size workaround hack removal tbd) * bench_seal_clear_storage pass * bench_seal_take_storage ... ok * added new seal_set_storage + updated benchmarks * added new seal_get_storage + updated benchmarks * added new seal_contains_storage + updated benchmarks * added tests for _transparent exec functions * wasm test for clear_storage * wasm test for take_storage * wasm test for new set_storage * wasm test for new get_storage * wasm test for new contains_storage * CI fix * ci fix * ci fix * ci fix * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * fixes according to the review feedback * tests & benchmarks fixed * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * refactoring * fix to runtime api * ci fix * ctx.get_storage() factored out * ctx.contains_storage() factored out * number of batches reduced for transparent hashing storage benchmarks * contracts RPC & pallet::get_storage to use transparent hashing * node and rpc updated to use get_storage with VarSizedKey * refactored (more concize) * refactored contains_storage (DRYed) * refactored contains_storage (DRYed) * fix rpc * fmt fix * more fixes in rpc * rollback `Pallet:get_storage` to Vec<u8> and rpc and node parts related to it * added `KeyDecodingFailed` error * Revert weird "fmt fix" This reverts commit c582cfff4b5cb2c9929fd5e3b45519bb24aeb657. * node-executor basic test update * fix node-executor basic test * benchmarks fix * more benchmarks fix * FixedSizedKey is hidden from pub, VarSizedKey is exported as StorageKey * ci fix * set_storage benchmark fix * ci fix * ci fix * comments improved * new error code to rpc: KEY_DECODING_FAILED * Put `rusty-cachier` before PR merge into `master` for `cargo-check-benches` job * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * minor optimization Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Parity Bot <admin@parity.io> Co-authored-by: Vladimir Istyufeev <vladimir@parity.io> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
7d2ecc8995
commit
68ea2ab039
@@ -18,15 +18,16 @@
|
||||
//! Environment definition of the wasm smart-contract runtime.
|
||||
|
||||
use crate::{
|
||||
exec::{ExecError, ExecResult, Ext, StorageKey, TopicOf},
|
||||
exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey},
|
||||
gas::{ChargedAmount, Token},
|
||||
schedule::HostFnWeights,
|
||||
wasm::env_def::ConvertibleToWasm,
|
||||
BalanceOf, CodeHash, Config, Error, SENTINEL,
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use codec::{Decode, DecodeAll, Encode, MaxEncodedLen};
|
||||
use frame_support::{dispatch::DispatchError, ensure, weights::Weight};
|
||||
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
|
||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
|
||||
use sp_core::{crypto::UncheckedFrom, Bytes};
|
||||
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
||||
@@ -35,6 +36,28 @@ use sp_sandbox::SandboxMemory;
|
||||
use sp_std::prelude::*;
|
||||
use wasm_instrument::parity_wasm::elements::ValueType;
|
||||
|
||||
/// Type of a storage key.
|
||||
#[allow(dead_code)]
|
||||
enum KeyType {
|
||||
/// Deprecated fix sized key [0;32].
|
||||
Fix,
|
||||
/// Variable sized key used in transparent hashing,
|
||||
/// cannot be larger than MaxStorageKeyLen.
|
||||
Variable(u32),
|
||||
}
|
||||
|
||||
impl KeyType {
|
||||
fn len<T: Config>(&self) -> Result<u32, TrapReason> {
|
||||
match self {
|
||||
KeyType::Fix => Ok(32u32),
|
||||
KeyType::Variable(len) => {
|
||||
ensure!(len <= &<T>::MaxStorageKeyLen::get(), Error::<T>::DecodingFailed);
|
||||
Ok(*len)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every error that can be returned to a contract when it calls any of the host functions.
|
||||
///
|
||||
/// # Note
|
||||
@@ -695,6 +718,7 @@ where
|
||||
|
||||
fn set_storage(
|
||||
&mut self,
|
||||
key_type: KeyType,
|
||||
key_ptr: u32,
|
||||
value_ptr: u32,
|
||||
value_len: u32,
|
||||
@@ -705,10 +729,21 @@ where
|
||||
if value_len > max_size {
|
||||
return Err(Error::<E::T>::ValueTooLarge.into())
|
||||
}
|
||||
let mut key: StorageKey = [0; 32];
|
||||
self.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
||||
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||
let value = Some(self.read_sandbox_memory(value_ptr, value_len)?);
|
||||
let write_outcome = self.ext.set_storage(key, value, false)?;
|
||||
let write_outcome = match key_type {
|
||||
KeyType::Fix => self.ext.set_storage(
|
||||
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
value,
|
||||
false,
|
||||
)?,
|
||||
KeyType::Variable(_) => self.ext.set_storage_transparent(
|
||||
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
value,
|
||||
false,
|
||||
)?,
|
||||
};
|
||||
|
||||
self.adjust_gas(
|
||||
charged,
|
||||
RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() },
|
||||
@@ -716,15 +751,70 @@ where
|
||||
Ok(write_outcome.old_len_with_sentinel())
|
||||
}
|
||||
|
||||
fn clear_storage(&mut self, key_ptr: u32) -> Result<u32, TrapReason> {
|
||||
fn clear_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
|
||||
let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
self.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
||||
let outcome = self.ext.set_storage(key, None, false)?;
|
||||
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||
let outcome = match key_type {
|
||||
KeyType::Fix => self.ext.set_storage(
|
||||
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
None,
|
||||
false,
|
||||
)?,
|
||||
KeyType::Variable(_) => self.ext.set_storage_transparent(
|
||||
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
None,
|
||||
false,
|
||||
)?,
|
||||
};
|
||||
|
||||
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len()));
|
||||
Ok(outcome.old_len_with_sentinel())
|
||||
}
|
||||
|
||||
fn get_storage(
|
||||
&mut self,
|
||||
key_type: KeyType,
|
||||
key_ptr: u32,
|
||||
out_ptr: u32,
|
||||
out_len_ptr: u32,
|
||||
) -> Result<ReturnCode, TrapReason> {
|
||||
let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?;
|
||||
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||
let outcome = match key_type {
|
||||
KeyType::Fix => self.ext.get_storage(
|
||||
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
),
|
||||
KeyType::Variable(_) => self.ext.get_storage_transparent(
|
||||
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
),
|
||||
};
|
||||
|
||||
if let Some(value) = outcome {
|
||||
self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
|
||||
self.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
||||
Ok(ReturnCode::Success)
|
||||
} else {
|
||||
self.adjust_gas(charged, RuntimeCosts::GetStorage(0));
|
||||
Ok(ReturnCode::KeyNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
|
||||
let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?;
|
||||
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
|
||||
let outcome = match key_type {
|
||||
KeyType::Fix => self.ext.get_storage_size(
|
||||
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
),
|
||||
KeyType::Variable(_) => self.ext.get_storage_size_transparent(
|
||||
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
||||
),
|
||||
};
|
||||
|
||||
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0)));
|
||||
Ok(outcome.unwrap_or(SENTINEL))
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
flags: CallFlags,
|
||||
@@ -863,11 +953,14 @@ define_env!(Env, <E: Ext>,
|
||||
// Equivalent to the newer version of `seal_set_storage` with the exception of the return
|
||||
// type. Still a valid thing to call when not interested in the return value.
|
||||
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
|
||||
ctx.set_storage(key_ptr, value_ptr, value_len).map(|_| ())
|
||||
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
},
|
||||
|
||||
// Set the value at the given key in the contract storage.
|
||||
//
|
||||
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||
// hashing, please use the newer version of this function.
|
||||
//
|
||||
// The value length must not exceed the maximum defined by the contracts module parameters.
|
||||
// Specifying a `value_len` of zero will store an empty value.
|
||||
//
|
||||
@@ -882,7 +975,27 @@ define_env!(Env, <E: Ext>,
|
||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||
// `SENTINEL` is returned as a sentinel value.
|
||||
[seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => {
|
||||
ctx.set_storage(key_ptr, value_ptr, value_len)
|
||||
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len)
|
||||
},
|
||||
|
||||
// Set the value at the given key in the contract storage.
|
||||
//
|
||||
// The key and value lengths must not exceed the maximums defined by the contracts module parameters.
|
||||
// Specifying a `value_len` of zero will store an empty value.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the location to store the value is placed.
|
||||
// - `key_len`: the length of the key in bytes.
|
||||
// - `value_ptr`: pointer into the linear memory where the value to set is placed.
|
||||
// - `value_len`: the length of the value in bytes.
|
||||
//
|
||||
// # Return Value
|
||||
//
|
||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||
// `SENTINEL` is returned as a sentinel value.
|
||||
[__unstable__] seal_set_storage(ctx, key_ptr: u32, key_len: u32, value_ptr: u32, value_len: u32) -> u32 => {
|
||||
ctx.set_storage(KeyType::Variable(key_len), key_ptr, value_ptr, value_len)
|
||||
},
|
||||
|
||||
// Clear the value at the given key in the contract storage.
|
||||
@@ -890,25 +1003,29 @@ define_env!(Env, <E: Ext>,
|
||||
// Equivalent to the newer version of `seal_clear_storage` with the exception of the return
|
||||
// type. Still a valid thing to call when not interested in the return value.
|
||||
[seal0] seal_clear_storage(ctx, key_ptr: u32) => {
|
||||
ctx.clear_storage(key_ptr).map(|_| ()).map_err(Into::into)
|
||||
ctx.clear_storage(KeyType::Fix, key_ptr).map(|_| ())
|
||||
},
|
||||
|
||||
// Clear the value at the given key in the contract storage.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the location to clear the value is placed.
|
||||
// - `key_ptr`: pointer into the linear memory where the key is placed.
|
||||
// - `key_len`: the length of the key in bytes.
|
||||
//
|
||||
// # Return Value
|
||||
//
|
||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||
// `SENTINEL` is returned as a sentinel value.
|
||||
[__unstable__] seal_clear_storage(ctx, key_ptr: u32) -> u32 => {
|
||||
ctx.clear_storage(key_ptr).map_err(Into::into)
|
||||
[__unstable__] seal_clear_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => {
|
||||
ctx.clear_storage(KeyType::Variable(key_len), key_ptr)
|
||||
},
|
||||
|
||||
// Retrieve the value under the given key from storage.
|
||||
//
|
||||
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||
// hashing, please use the newer version of this function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||
@@ -920,21 +1037,36 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// `ReturnCode::KeyNotFound`
|
||||
[seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||
let charged = ctx.charge_gas(RuntimeCosts::GetStorage(ctx.ext.max_value_size()))?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
||||
if let Some(value) = ctx.ext.get_storage(&key) {
|
||||
ctx.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
|
||||
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
||||
Ok(ReturnCode::Success)
|
||||
} else {
|
||||
ctx.adjust_gas(charged, RuntimeCosts::GetStorage(0));
|
||||
Ok(ReturnCode::KeyNotFound)
|
||||
}
|
||||
ctx.get_storage(KeyType::Fix, key_ptr, out_ptr, out_len_ptr)
|
||||
},
|
||||
|
||||
// Retrieve the value under the given key from storage.
|
||||
//
|
||||
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||
// hashing, please use the newer version of this function.
|
||||
//
|
||||
// The key length must not exceed the maximum defined by the contracts module parameter.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||
// - `key_len`: the length of the key in bytes.
|
||||
// - `out_ptr`: pointer to the linear memory where the value is written to.
|
||||
// - `out_len_ptr`: in-out pointer into linear memory where the buffer length
|
||||
// is read from and the value length is written to.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// `ReturnCode::KeyNotFound`
|
||||
[__unstable__] seal_get_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||
ctx.get_storage(KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr)
|
||||
},
|
||||
|
||||
// Checks whether there is a value stored under the given key.
|
||||
//
|
||||
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
|
||||
// hashing, please use the newer version of this function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||
@@ -944,16 +1076,24 @@ define_env!(Env, <E: Ext>,
|
||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||
// `SENTINEL` is returned as a sentinel value.
|
||||
[seal0] seal_contains_storage(ctx, key_ptr: u32) -> u32 => {
|
||||
let charged = ctx.charge_gas(RuntimeCosts::ContainsStorage(ctx.ext.max_value_size()))?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
||||
if let Some(len) = ctx.ext.get_storage_size(&key) {
|
||||
ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(len));
|
||||
Ok(len)
|
||||
} else {
|
||||
ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(0));
|
||||
Ok(SENTINEL)
|
||||
}
|
||||
ctx.contains_storage(KeyType::Fix, key_ptr)
|
||||
},
|
||||
|
||||
// Checks whether there is a value stored under the given key.
|
||||
//
|
||||
// The key length must not exceed the maximum defined by the contracts module parameter.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||
// - `key_len`: the length of the key in bytes.
|
||||
//
|
||||
// # Return Value
|
||||
//
|
||||
// Returns the size of the pre-existing value at the specified key if any. Otherwise
|
||||
// `SENTINEL` is returned as a sentinel value.
|
||||
[__unstable__] seal_contains_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => {
|
||||
ctx.contains_storage(KeyType::Variable(key_len), key_ptr)
|
||||
},
|
||||
|
||||
// Retrieve and remove the value under the given key from storage.
|
||||
@@ -961,6 +1101,7 @@ define_env!(Env, <E: Ext>,
|
||||
// # Parameters
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the key of the requested value is placed.
|
||||
// - `key_len`: the length of the key in bytes.
|
||||
// - `out_ptr`: pointer to the linear memory where the value is written to.
|
||||
// - `out_len_ptr`: in-out pointer into linear memory where the buffer length
|
||||
// is read from and the value length is written to.
|
||||
@@ -968,11 +1109,10 @@ define_env!(Env, <E: Ext>,
|
||||
// # Errors
|
||||
//
|
||||
// `ReturnCode::KeyNotFound`
|
||||
[__unstable__] seal_take_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||
[__unstable__] seal_take_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||
let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
|
||||
if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage(key, None, true)? {
|
||||
let key = ctx.read_sandbox_memory(key_ptr, key_len)?;
|
||||
if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage_transparent(&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?, None, true)? {
|
||||
ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32));
|
||||
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
|
||||
Ok(ReturnCode::Success)
|
||||
|
||||
Reference in New Issue
Block a user