seal: Add automated weights for contract API calls (#7017)

* seal: Add capability to put uninstrumented code (for benchmarks)

Benchmarks should only measure the overhead of the API calls itself.
For that reason we want to run them without instrumentation.

* seal: Cap the the data length for deposited events

Data used in events has storage implications for archive nodes.
Those need to keep the events in storage forever. For that reason
we want to limit the amount of storage that can be used inside events.

* seal: Fix error reporting in the case out of bound sandbox access

* seal: Refactor existing benchmarks

* seal: Convert benchmark file to tabs

* seal: Add benchmarks for functions called by contracts

* seal: Create a default schedule from benchmark generated WeightInfo

* seal: Make use of WeightInfo in extrinsic weight annotations

* seal: Replace the old schedule by the benchmark generated one

* Review: Fix copy paste typo in schedule construction

* Review: Fix stale docs

* Fix whitespace errors

Co-authored-by: Sergei Shulepov <sergei@parity.io>

* Review: Use checked_div in order to be more defensive

* Review: Rename no_charge to already_charged

* Review: Whitelist caller of extrinsics

* Review: Remove trailing whitespace

* Review: Remove confusing "self::" syntax

* Review: Add docs for the benchmark prepration submodule

* Review: Move code generation functions to own module

* Review: Refactor and document benchmark helper functions

* Remove additional empty line

* Added missing comment on caller_funding

* Update frame/contracts/src/benchmarking/code.rs

Co-authored-by: Sergei Shulepov <sergei@parity.io>

* Fix missing sp_std::prelude import in code.rs

* cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_contracts --heap-pages 4096

* Use weights from the benchmark machine for the substrate node

* Remove prefixes from Schedule members

* Data lengths in the WeightInfo Trait are specified in kilobytes

* Rename ApiWeights to HostFunctionWeights

Co-authored-by: Sergei Shulepov <sergei@parity.io>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Alexander Theißen
2020-10-08 17:36:37 +02:00
committed by GitHub
parent e3682fa2f4
commit 443725f0f6
20 changed files with 3460 additions and 920 deletions
+31 -128
View File
@@ -78,6 +78,7 @@
//! * [Balances](../pallet_balances/index.html)
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="256")]
#[macro_use]
mod gas;
@@ -86,6 +87,8 @@ mod exec;
mod wasm;
mod rent;
mod benchmarking;
mod schedule;
mod weight_info;
#[cfg(test)]
mod tests;
@@ -96,9 +99,9 @@ use crate::wasm::{WasmLoader, WasmVm};
pub use crate::gas::{Gas, GasMeter};
pub use crate::exec::{ExecResult, ExecReturnValue};
pub use crate::wasm::ReturnCode as RuntimeReturnCode;
pub use crate::weight_info::WeightInfo;
pub use crate::schedule::{Schedule, HostFnWeights, InstructionWeights};
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
use sp_core::crypto::UncheckedFrom;
use sp_std::{prelude::*, marker::PhantomData, fmt::Debug};
use codec::{Codec, Encode, Decode};
@@ -367,12 +370,16 @@ pub trait Trait: frame_system::Trait {
/// The maximum nesting level of a call/instantiate stack.
type MaxDepth: Get<u32>;
/// The maximum size of a storage value in bytes.
/// The maximum size of a storage value and event payload in bytes.
type MaxValueSize: Get<u32>;
/// Used to answer contracts's queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
/// Describes the weights of the dispatchables of this module and is also used to
/// construct a default cost schedule.
type WeightInfo: WeightInfo;
}
/// Simple contract address determiner.
@@ -445,6 +452,8 @@ decl_error! {
DecodingFailed,
/// Contract trapped during execution.
ContractTrapped,
/// The size defined in `T::MaxValueSize` was exceeded.
ValueTooLarge,
}
}
@@ -498,8 +507,8 @@ decl_module! {
/// Updates the schedule for metering contracts.
///
/// The schedule must have a greater version than the stored schedule.
#[weight = 0]
pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult {
#[weight = T::WeightInfo::update_schedule()]
pub fn update_schedule(origin, schedule: Schedule<T>) -> DispatchResult {
ensure_root(origin)?;
if <Module<T>>::current_schedule().version >= schedule.version {
Err(Error::<T>::InvalidScheduleVersion)?
@@ -513,7 +522,7 @@ decl_module! {
/// Stores the given binary Wasm code into the chain's storage and returns its `codehash`.
/// You can instantiate contracts only with stored code.
#[weight = Module::<T>::calc_code_put_costs(&code)]
#[weight = T::WeightInfo::put_code(code.len() as u32 / 1024)]
pub fn put_code(
origin,
code: Vec<u8>
@@ -535,7 +544,7 @@ decl_module! {
/// * If the account is a regular account, any value will be transferred.
/// * If no account exists and the call value is not less than `existential_deposit`,
/// a regular account will be created and any value will be transferred.
#[weight = *gas_limit]
#[weight = T::WeightInfo::call().saturating_add(*gas_limit)]
pub fn call(
origin,
dest: <T::Lookup as StaticLookup>::Source,
@@ -563,7 +572,7 @@ decl_module! {
/// after the execution is saved as the `code` of the account. That code will be invoked
/// upon any call received by this account.
/// - The contract is initialized.
#[weight = *gas_limit]
#[weight = T::WeightInfo::instantiate(data.len() as u32 / 1024).saturating_add(*gas_limit)]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
@@ -586,7 +595,7 @@ decl_module! {
///
/// If contract is not evicted as a result of this call, no actions are taken and
/// the sender is not eligible for the reward.
#[weight = 0]
#[weight = T::WeightInfo::claim_surcharge()]
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
@@ -659,17 +668,21 @@ impl<T: Trait> Module<T> {
) -> sp_std::result::Result<RentProjection<T::BlockNumber>, ContractAccessError> {
rent::compute_rent_projection::<T>(&address)
}
/// Put code for benchmarks which does not check or instrument the code.
#[cfg(feature = "runtime-benchmarks")]
pub fn put_code_raw(code: Vec<u8>) -> DispatchResult {
let schedule = <Module<T>>::current_schedule();
let result = wasm::save_code_raw::<T>(code, &schedule);
result.map(|_| ()).map_err(Into::into)
}
}
impl<T: Trait> Module<T> {
fn calc_code_put_costs(code: &Vec<u8>) -> Gas {
<Module<T>>::current_schedule().put_code_per_byte_cost.saturating_mul(code.len() as Gas)
}
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm<T>, WasmLoader<T>>, &mut GasMeter<T>) -> ExecResult,
) -> ExecResult {
let cfg = Config::preload();
let vm = WasmVm::new(&cfg.schedule);
@@ -691,7 +704,7 @@ decl_event! {
/// Contract has been evicted and is now in tombstone state.
/// \[contract, tombstone\]
///
///
/// # Params
///
/// - `contract`: `AccountId`: The account ID of the evicted contract.
@@ -700,7 +713,7 @@ decl_event! {
/// Restoration for a contract has been successful.
/// \[donor, dest, code_hash, rent_allowance\]
///
///
/// # Params
///
/// - `donor`: `AccountId`: Account ID of the restoring contract
@@ -725,7 +738,7 @@ decl_event! {
decl_storage! {
trait Store for Module<T: Trait> as Contracts {
/// Current cost schedule for contracts.
CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default();
CurrentSchedule get(fn current_schedule) config(): Schedule<T> = Default::default();
/// A mapping from an original code hash to the original code, untouched by instrumentation.
pub PristineCode: map hasher(identity) CodeHash<T> => Option<Vec<u8>>;
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
@@ -744,7 +757,7 @@ decl_storage! {
/// We assume that these values can't be changed in the
/// course of transaction execution.
pub struct Config<T: Trait> {
pub schedule: Schedule,
pub schedule: Schedule<T>,
pub existential_deposit: BalanceOf<T>,
pub tombstone_deposit: BalanceOf<T>,
pub max_depth: u32,
@@ -781,113 +794,3 @@ impl<T: Trait> Config<T> {
T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get())
}
}
/// Definition of the cost schedule and other parameterizations for wasm vm.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Schedule {
/// Version of the schedule.
pub version: u32,
/// Cost of putting a byte of code into storage.
pub put_code_per_byte_cost: Gas,
/// Gas cost of a growing memory by single page.
pub grow_mem_cost: Gas,
/// Gas cost of a regular operation.
pub regular_op_cost: Gas,
/// Gas cost per one byte returned.
pub return_data_per_byte_cost: Gas,
/// Gas cost to deposit an event; the per-byte portion.
pub event_data_per_byte_cost: Gas,
/// Gas cost to deposit an event; the cost per topic.
pub event_per_topic_cost: Gas,
/// Gas cost to deposit an event; the base.
pub event_base_cost: Gas,
/// Base gas cost to call into a contract.
pub call_base_cost: Gas,
/// Base gas cost to instantiate a contract.
pub instantiate_base_cost: Gas,
/// Base gas cost to dispatch a runtime call.
pub dispatch_base_cost: Gas,
/// Gas cost per one byte read from the sandbox memory.
pub sandbox_data_read_cost: Gas,
/// Gas cost per one byte written to the sandbox memory.
pub sandbox_data_write_cost: Gas,
/// Cost for a simple balance transfer.
pub transfer_cost: Gas,
/// Cost for instantiating a new contract.
pub instantiate_cost: Gas,
/// The maximum number of topics supported by an event.
pub max_event_topics: u32,
/// Maximum allowed stack height.
///
/// See https://wiki.parity.io/WebAssembly-StackHeight to find out
/// how the stack frame cost is calculated.
pub max_stack_height: u32,
/// Maximum number of memory pages allowed for a contract.
pub max_memory_pages: u32,
/// Maximum allowed size of a declared table.
pub max_table_size: u32,
/// Whether the `seal_println` function is allowed to be used contracts.
/// MUST only be enabled for `dev` chains, NOT for production chains
pub enable_println: bool,
/// The maximum length of a subject used for PRNG generation.
pub max_subject_len: u32,
/// The maximum length of a contract code in bytes. This limit applies to the uninstrumented
// and pristine form of the code as supplied to `put_code`.
pub max_code_size: u32,
}
// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi
// This is a wild guess and should be viewed as a rough estimation.
// Proper benchmarks are needed before this value and its derivatives can be used in production.
const WASM_INSTRUCTION_COST: Gas = 500_000;
impl Default for Schedule {
fn default() -> Schedule {
Schedule {
version: 0,
put_code_per_byte_cost: WASM_INSTRUCTION_COST,
grow_mem_cost: WASM_INSTRUCTION_COST,
regular_op_cost: WASM_INSTRUCTION_COST,
return_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_per_topic_cost: WASM_INSTRUCTION_COST,
event_base_cost: WASM_INSTRUCTION_COST,
call_base_cost: 135 * WASM_INSTRUCTION_COST,
dispatch_base_cost: 135 * WASM_INSTRUCTION_COST,
instantiate_base_cost: 175 * WASM_INSTRUCTION_COST,
sandbox_data_read_cost: WASM_INSTRUCTION_COST,
sandbox_data_write_cost: WASM_INSTRUCTION_COST,
transfer_cost: 100 * WASM_INSTRUCTION_COST,
instantiate_cost: 200 * WASM_INSTRUCTION_COST,
max_event_topics: 4,
max_stack_height: 64 * 1024,
max_memory_pages: 16,
max_table_size: 16 * 1024,
enable_println: false,
max_subject_len: 32,
max_code_size: 512 * 1024,
}
}
}