[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:
Alexander Gryaznov
2022-06-23 15:10:35 +03:00
committed by GitHub
parent 7d2ecc8995
commit 68ea2ab039
13 changed files with 2200 additions and 1158 deletions
+180 -40
View File
@@ -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)