Integrate pallet_contracts gas with the weight system (#5712)

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-Authored-By: Sergei Pepyakin <sergei@parity.io>
This commit is contained in:
Alexander Theißen
2020-04-24 10:48:13 +02:00
committed by GitHub
parent e731817e24
commit 000c924b62
20 changed files with 386 additions and 680 deletions
+80 -225
View File
@@ -64,15 +64,6 @@
//! initialize the contract.
//! * `call` - Makes a call to an account, optionally transferring some balance.
//!
//! ### Signed Extensions
//!
//! The contracts module defines the following extension:
//!
//! - [`CheckBlockGasLimit`]: Ensures that the transaction does not exceeds the block gas limit.
//!
//! The signed extension needs to be added as signed extra to the transaction type to be used in the
//! runtime.
//!
//! ## Usage
//!
//! The Contract module is a work in progress. The following examples show how this Contract module
@@ -114,21 +105,20 @@ use codec::{Codec, Encode, Decode};
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{
Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member, SignedExtension,
DispatchInfoOf,
},
transaction_validity::{
ValidTransaction, InvalidTransaction, TransactionValidity, TransactionValidityError,
Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member,
},
RuntimeDebug,
};
use frame_support::dispatch::{DispatchResult, Dispatchable};
use frame_support::dispatch::{
PostDispatchInfo, DispatchResult, Dispatchable, DispatchResultWithPostInfo
};
use frame_support::weights::MINIMUM_WEIGHT;
use frame_support::{
Parameter, decl_module, decl_event, decl_storage, decl_error,
parameter_types, IsSubType, storage::child::{self, ChildInfo},
};
use frame_support::traits::{OnUnbalanced, Currency, Get, Time, Randomness};
use frame_support::weights::{FunctionOf, DispatchClass, Weight, GetDispatchInfo, Pays};
use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root};
use pallet_contracts_primitives::{RentProjection, ContractAccessError};
@@ -295,9 +285,9 @@ where
}
}
pub type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub type BalanceOf<T> = <<T as pallet_transaction_payment::Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
<<T as pallet_transaction_payment::Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
parameter_types! {
/// A reasonable default value for [`Trait::SignedClaimedHandicap`].
@@ -312,35 +302,21 @@ parameter_types! {
pub const DefaultRentDepositOffset: u32 = 1000;
/// A reasonable default value for [`Trait::SurchargeReward`].
pub const DefaultSurchargeReward: u32 = 150;
/// A reasonable default value for [`Trait::TransferFee`].
pub const DefaultTransferFee: u32 = 0;
/// A reasonable default value for [`Trait::InstantiationFee`].
pub const DefaultInstantiationFee: u32 = 0;
/// A reasonable default value for [`Trait::TransactionBaseFee`].
pub const DefaultTransactionBaseFee: u32 = 0;
/// A reasonable default value for [`Trait::TransactionByteFee`].
pub const DefaultTransactionByteFee: u32 = 0;
/// A reasonable default value for [`Trait::ContractFee`].
pub const DefaultContractFee: u32 = 21;
/// A reasonable default value for [`Trait::CallBaseFee`].
pub const DefaultCallBaseFee: u32 = 1000;
/// A reasonable default value for [`Trait::InstantiateBaseFee`].
pub const DefaultInstantiateBaseFee: u32 = 1000;
/// A reasonable default value for [`Trait::MaxDepth`].
pub const DefaultMaxDepth: u32 = 32;
/// A reasonable default value for [`Trait::MaxValueSize`].
pub const DefaultMaxValueSize: u32 = 16_384;
/// A reasonable default value for [`Trait::BlockGasLimit`].
pub const DefaultBlockGasLimit: u32 = 10_000_000;
}
pub trait Trait: frame_system::Trait {
type Currency: Currency<Self::AccountId>;
pub trait Trait: frame_system::Trait + pallet_transaction_payment::Trait {
type Time: Time;
type Randomness: Randomness<Self::Hash>;
/// The outer call dispatch type.
type Call: Parameter + Dispatchable<Origin=<Self as frame_system::Trait>::Origin> + IsSubType<Module<Self>, Self>;
type Call:
Parameter +
Dispatchable<PostInfo=PostDispatchInfo, Origin=<Self as frame_system::Trait>::Origin> +
IsSubType<Module<Self>, Self> + GetDispatchInfo;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
@@ -348,18 +324,9 @@ pub trait Trait: frame_system::Trait {
/// A function type to get the contract address given the instantiator.
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
/// A function type that computes the fee for dispatching the given `Call`.
///
/// It is recommended (though not required) for this function to return a fee that would be
/// taken by the Executive module for regular dispatch.
type ComputeDispatchFee: ComputeDispatchFee<<Self as Trait>::Call, BalanceOf<Self>>;
/// trie id generator
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
/// Handler for the unbalanced reduction when making a gas payment.
type GasPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Handler for rent payments.
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
@@ -392,29 +359,11 @@ pub trait Trait: frame_system::Trait {
/// to removal of a contract.
type SurchargeReward: Get<BalanceOf<Self>>;
/// The fee to be paid for making a transaction; the base.
type TransactionBaseFee: Get<BalanceOf<Self>>;
/// The fee to be paid for making a transaction; the per-byte portion.
type TransactionByteFee: Get<BalanceOf<Self>>;
/// The fee required to instantiate a contract instance.
type ContractFee: Get<BalanceOf<Self>>;
/// The base fee charged for calling into a contract.
type CallBaseFee: Get<Gas>;
/// The base fee charged for instantiating a contract.
type InstantiateBaseFee: Get<Gas>;
/// The maximum nesting level of a call/instantiate stack.
type MaxDepth: Get<u32>;
/// The maximum size of a storage value in bytes.
type MaxValueSize: Get<u32>;
/// The maximum amount of gas that could be expended per block.
type BlockGasLimit: Get<Gas>;
}
/// Simple contract address determiner.
@@ -440,19 +389,6 @@ where
}
}
/// The default dispatch fee computor computes the fee in the same way that
/// the implementation of `ChargeTransactionPayment` for the Balances module does. Note that this only takes a fixed
/// fee based on size. Unlike the balances module, weight-fee is applied.
pub struct DefaultDispatchFeeComputor<T: Trait>(PhantomData<T>);
impl<T: Trait> ComputeDispatchFee<<T as Trait>::Call, BalanceOf<T>> for DefaultDispatchFeeComputor<T> {
fn compute_dispatch_fee(call: &<T as Trait>::Call) -> BalanceOf<T> {
let encoded_len = call.using_encoded(|encoded| encoded.len() as u32);
let base_fee = T::TransactionBaseFee::get();
let byte_fee = T::TransactionByteFee::get();
base_fee + byte_fee * encoded_len.into()
}
}
decl_error! {
/// Error for the contracts module.
pub enum Error for Module<T: Trait> {
@@ -505,24 +441,6 @@ decl_module! {
/// to removal of a contract.
const SurchargeReward: BalanceOf<T> = T::SurchargeReward::get();
/// The fee to be paid for making a transaction; the base.
const TransactionBaseFee: BalanceOf<T> = T::TransactionBaseFee::get();
/// The fee to be paid for making a transaction; the per-byte portion.
const TransactionByteFee: BalanceOf<T> = T::TransactionByteFee::get();
/// The fee required to instantiate a contract instance. A reasonable default value
/// is 21.
const ContractFee: BalanceOf<T> = T::ContractFee::get();
/// The base fee charged for calling into a contract. A reasonable default
/// value is 135.
const CallBaseFee: Gas = T::CallBaseFee::get();
/// The base fee charged for instantiating a contract. A reasonable default value
/// is 175.
const InstantiateBaseFee: Gas = T::InstantiateBaseFee::get();
/// The maximum nesting level of a call/instantiate stack. A reasonable default
/// value is 100.
const MaxDepth: u32 = T::MaxDepth::get();
@@ -530,10 +448,6 @@ decl_module! {
/// The maximum size of a storage value in bytes. A reasonable default is 16 KiB.
const MaxValueSize: u32 = T::MaxValueSize::get();
/// The maximum amount of gas that could be expended per block. A reasonable
/// default value is 10_000_000.
const BlockGasLimit: Gas = T::BlockGasLimit::get();
fn deposit_event() = default;
/// Updates the schedule for metering contracts.
@@ -554,24 +468,21 @@ 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 = MINIMUM_WEIGHT]
#[weight = FunctionOf(
|args: (&Vec<u8>,)| Module::<T>::calc_code_put_costs(args.0) + MINIMUM_WEIGHT,
DispatchClass::Normal,
Pays::Yes
)]
pub fn put_code(
origin,
#[compact] gas_limit: Gas,
code: Vec<u8>
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let (mut gas_meter, imbalance) = gas::buy_gas::<T>(&origin, gas_limit)?;
ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
let result = wasm::save_code::<T>(code, &mut gas_meter, &schedule);
let result = wasm::save_code::<T>(code, &schedule);
if let Ok(code_hash) = result {
Self::deposit_event(RawEvent::CodeStored(code_hash));
}
gas::refund_unused_gas::<T>(&origin, gas_meter, imbalance);
result.map(|_| ()).map_err(Into::into)
}
@@ -582,20 +493,27 @@ 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 = MINIMUM_WEIGHT]
#[weight = FunctionOf(
|args: (&<T::Lookup as StaticLookup>::Source, &BalanceOf<T>, &Weight, &Vec<u8>)|
args.2 + MINIMUM_WEIGHT,
DispatchClass::Normal,
Pays::Yes
)]
pub fn call(
origin,
dest: <T::Lookup as StaticLookup>::Source,
#[compact] value: BalanceOf<T>,
#[compact] gas_limit: Gas,
data: Vec<u8>
) -> DispatchResult {
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
Self::bare_call(origin, dest, value, gas_limit, data)
.map(|_| ())
.map_err(|e| e.reason.into())
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, data)
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
}
/// Instantiates a new contract from the `codehash` generated by `put_code`, optionally transferring some balance.
@@ -608,22 +526,26 @@ 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 = MINIMUM_WEIGHT]
#[weight = FunctionOf(
|args: (&BalanceOf<T>, &Weight, &CodeHash<T>, &Vec<u8>)| args.1 + MINIMUM_WEIGHT,
DispatchClass::Normal,
Pays::Yes
)]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code_hash: CodeHash<T>,
data: Vec<u8>
) -> DispatchResult {
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| {
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.instantiate(endowment, gas_meter, &code_hash, data)
.map(|(_address, output)| output)
})
.map(|_| ())
.map_err(|e| e.reason.into())
});
gas_meter.into_dispatch_result(result.map_err(|e| e.reason))
}
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
@@ -658,10 +580,6 @@ decl_module! {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
}
}
fn on_finalize() {
GasSpent::kill();
}
}
}
@@ -678,7 +596,8 @@ impl<T: Trait> Module<T> {
gas_limit: Gas,
input_data: Vec<u8>,
) -> ExecResult {
Self::execute_wasm(origin, gas_limit, |ctx, gas_meter| {
let mut gas_meter = GasMeter::new(gas_limit);
Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
})
}
@@ -710,40 +629,27 @@ impl<T: Trait> Module<T> {
}
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_limit: Gas,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult
) -> ExecResult {
// Pay for the gas upfront.
//
// NOTE: it is very important to avoid any state changes before
// paying for the gas.
let (mut gas_meter, imbalance) =
try_or_exec_error!(
gas::buy_gas::<T>(&origin, gas_limit),
// We don't have a spare buffer here in the first place, so create a new empty one.
Vec::new()
);
let cfg = Config::preload();
let vm = WasmVm::new(&cfg.schedule);
let loader = WasmLoader::new(&cfg.schedule);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
let result = func(&mut ctx, &mut gas_meter);
let result = func(&mut ctx, gas_meter);
if result.as_ref().map(|output| output.is_success()).unwrap_or(false) {
// Commit all changes that made it thus far into the persistent storage.
DirectAccountDb.commit(ctx.overlay.into_change_set());
}
// Refund cost of the unused gas.
//
// NOTE: This should go after the commit to the storage, since the storage changes
// can alter the balance of the caller.
gas::refund_unused_gas::<T>(&origin, gas_meter, imbalance);
// Execute deferred actions.
ctx.deferred.into_iter().for_each(|deferred| {
use self::exec::DeferredAction::*;
@@ -759,7 +665,13 @@ impl<T: Trait> Module<T> {
origin: who,
call,
} => {
let info = call.get_dispatch_info();
let result = call.dispatch(RawOrigin::Signed(who.clone()).into());
let post_info = match result {
Ok(post_info) => post_info,
Err(err) => err.post_info,
};
gas_meter.refund(post_info.calc_unspent(&info));
Self::deposit_event(RawEvent::Dispatched(who, result.is_ok()));
}
RestoreTo {
@@ -917,8 +829,6 @@ decl_event! {
decl_storage! {
trait Store for Module<T: Trait> as Contracts {
/// Gas spent so far in this block.
GasSpent get(fn gas_spent): Gas;
/// Current cost schedule for contracts.
CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default();
/// A mapping from an original code hash to the original code, untouched by instrumentation.
@@ -929,8 +839,6 @@ decl_storage! {
pub AccountCounter: u64 = 0;
/// The code associated with a given account.
pub ContractInfoOf: map hasher(twox_64_concat) T::AccountId => Option<ContractInfo<T>>;
/// The price of one unit of gas.
GasPrice get(fn gas_price) config(): BalanceOf<T> = 1.into();
}
}
@@ -944,7 +852,6 @@ pub struct Config<T: Trait> {
pub tombstone_deposit: BalanceOf<T>,
pub max_depth: u32,
pub max_value_size: u32,
pub contract_account_instantiate_fee: BalanceOf<T>,
}
impl<T: Trait> Config<T> {
@@ -955,7 +862,6 @@ impl<T: Trait> Config<T> {
tombstone_deposit: T::TombstoneDeposit::get(),
max_depth: T::MaxDepth::get(),
max_value_size: T::MaxValueSize::get(),
contract_account_instantiate_fee: T::ContractFee::get(),
}
}
}
@@ -994,6 +900,9 @@ pub struct Schedule {
/// 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,
@@ -1003,6 +912,9 @@ pub struct Schedule {
/// 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,
@@ -1026,22 +938,29 @@ pub struct Schedule {
pub max_subject_len: 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: 1,
grow_mem_cost: 1,
regular_op_cost: 1,
return_data_per_byte_cost: 1,
event_data_per_byte_cost: 1,
event_per_topic_cost: 1,
event_base_cost: 1,
call_base_cost: 135,
instantiate_base_cost: 175,
sandbox_data_read_cost: 1,
sandbox_data_write_cost: 1,
transfer_cost: 100,
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,
@@ -1052,67 +971,3 @@ impl Default for Schedule {
}
}
/// `SignedExtension` that checks if a transaction would exhausts the block gas limit.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckBlockGasLimit<T: Trait + Send + Sync>(PhantomData<T>);
impl<T: Trait + Send + Sync> Default for CheckBlockGasLimit<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckBlockGasLimit<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckBlockGasLimit")
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckBlockGasLimit<T> {
const IDENTIFIER: &'static str = "CheckBlockGasLimit";
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
fn validate(
&self,
_: &Self::AccountId,
call: &Self::Call,
_: &DispatchInfoOf<Self::Call>,
_: usize,
) -> TransactionValidity {
let call = match call.is_sub_type() {
Some(call) => call,
None => return Ok(ValidTransaction::default()),
};
match call {
Call::claim_surcharge(_, _) | Call::update_schedule(_) =>
Ok(ValidTransaction::default()),
Call::put_code(gas_limit, _)
| Call::call(_, _, gas_limit, _)
| Call::instantiate(_, gas_limit, _, _)
=> {
// Check if the specified amount of gas is available in the current block.
// This cannot underflow since `gas_spent` is never greater than `T::BlockGasLimit`.
let gas_available = T::BlockGasLimit::get() - <Module<T>>::gas_spent();
if *gas_limit > gas_available {
// gas limit reached, revert the transaction and retry again in the future
InvalidTransaction::ExhaustsResources.into()
} else {
Ok(ValidTransaction::default())
}
},
Call::__PhantomItem(_, _) => unreachable!("Variant is never constructed"),
}
}
}