Custom runtime module errors (#3433)

* srml-system checks

* wip

* more modules compiles

* node-runtime checks

* build.sh passes

* include dispatch error in failed event

* revert some unnecessary changes

* refactor based on comments

* more compile error fixes

* avoid unnecessary into

* reorder code

* fixes some tests

* manually implement encode & decode to avoid i8 workaround

* more test fixes

* more fixes

* more error fixes

* Apply suggestions from code review

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* address comments

* test for DispatchError encoding

* tyep alias for democracy

* make error printable

* line width

* fix balances tests

* fix executive test

* fix system tests

* bump version

* ensure consistent method signature

* Apply suggestions from code review

Co-Authored-By: Gavin Wood <github@gavwood.com>

* changes based on review

* Add issue number for TODOs

* fix

* line width

* fix test

* Update core/sr-primitives/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update core/sr-primitives/src/traits.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/council/src/motions.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/council/src/motions.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* update based on review

* More concrete macro matching

* fix test build issue

* Update hex-literal dependency version. (#3141)

* Update hex-literal dep version.

* Update lock file.

* Start to rework the new error handling

* More work to get it back compiling

* Start to fix after master merge

* The great transaction error handling refactoring

* Make `decl_error` errors convertible to `&'static str`

* Make srml-executive build again

* Fix `sr-primitives` tests

* More fixes

* Last round of fix ups

* Fix build

* Fix build

* Apply suggestions from code review

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Rename some stuff

* Fixes after master merge

* Adds `CheckBlockGasLimit` signed extension

* Remove debug stuff

* Fix srml-balances test

* Rename `InvalidIndex` to `CannotLookup`

* Remove weird generic parameters

* Rename function again

* Fix import

* Document the signed extension

* Change from `Into` to `From`

* Update srml/contracts/src/lib.rs

Co-Authored-By: Sergei Pepyakin <sergei@parity.io>

* Fix compilation

* Update srml/contracts/src/lib.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update core/sr-primitives/src/transaction_validity.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Remove unused code

* Fix compilation

* Some cleanups

* Fix compile errors

* Make `TransactionValidity` a `Result`

* Apply suggestions from code review

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Beautify the code a little bit and fix test

* Make `CannotLookup` an inherent error declared by `decl_error!`

* Adds some documentation

* Make `ApplyOutcome` a result

* Up the spec_version

* Apply suggestions from code review

Co-Authored-By: Gavin Wood <gavin@parity.io>
Co-Authored-By: DemiMarie-parity <48690212+DemiMarie-parity@users.noreply.github.com>
This commit is contained in:
Bastian Köcher
2019-09-04 16:21:42 +02:00
committed by GitHub
parent 5e4bc7c9b6
commit c6f3798078
46 changed files with 1259 additions and 630 deletions
+6 -12
View File
@@ -16,10 +16,12 @@
use crate::{GasSpent, Module, Trait, BalanceOf, NegativeImbalanceOf};
use rstd::convert::TryFrom;
use sr_primitives::BLOCK_FULL;
use sr_primitives::traits::{CheckedMul, Zero, SaturatedConversion, SimpleArithmetic, UniqueSaturatedInto};
use support::StorageValue;
use support::traits::{Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReason};
use sr_primitives::traits::{
CheckedMul, Zero, SaturatedConversion, SimpleArithmetic, UniqueSaturatedInto,
};
use support::{
traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReason}, StorageValue,
};
#[cfg(test)]
use std::{any::Any, fmt::Debug};
@@ -200,14 +202,6 @@ pub fn buy_gas<T: Trait>(
transactor: &T::AccountId,
gas_limit: Gas,
) -> Result<(GasMeter<T>, NegativeImbalanceOf<T>), &'static str> {
// 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
return Err(BLOCK_FULL);
}
// Buy the specified amount of gas.
let gas_price = <Module<T>>::gas_price();
let cost = if gas_price.is_zero() {
+85 -11
View File
@@ -63,6 +63,15 @@
//! This creates a new smart contract account and calls its contract deploy handler to 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 can be
@@ -100,15 +109,19 @@ use primitives::crypto::UncheckedFrom;
use rstd::{prelude::*, marker::PhantomData};
use codec::{Codec, Encode, Decode};
use runtime_io::blake2_256;
use sr_primitives::traits::{
Hash, StaticLookup, Zero, MaybeSerializeDebug, Member
use sr_primitives::{
traits::{Hash, StaticLookup, Zero, MaybeSerializeDebug, Member, SignedExtension},
weights::DispatchInfo,
transaction_validity::{
ValidTransaction, InvalidTransaction, TransactionValidity, TransactionValidityError,
},
};
use support::dispatch::{Result, Dispatchable};
use support::{
Parameter, StorageMap, StorageValue, decl_module, decl_event, decl_storage, storage::child,
parameter_types,
};
use support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency, Get};
use support::{traits::{OnFreeBalanceZero, OnUnbalanced, Currency, Get}, IsSubType};
use system::{ensure_signed, RawOrigin, ensure_root};
use primitives::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
use timestamp;
@@ -321,7 +334,7 @@ pub trait Trait: timestamp::Trait {
type Currency: Currency<Self::AccountId>;
/// The outer call dispatch type.
type Call: Parameter + Dispatchable<Origin=<Self as system::Trait>::Origin>;
type Call: Parameter + Dispatchable<Origin=<Self as system::Trait>::Origin> + IsSubType<Module<Self>, Self>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
@@ -597,15 +610,17 @@ decl_module! {
/// the sender is not eligible for the reward.
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
let origin = origin.into();
let (signed, rewarded) = match origin {
Ok(system::RawOrigin::Signed(ref account)) if aux_sender.is_none() => {
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(system::RawOrigin::Signed(account)), None) => {
(true, account)
},
Ok(system::RawOrigin::None) if aux_sender.is_some() => {
(false, aux_sender.as_ref().expect("checked above"))
(Ok(system::RawOrigin::None), Some(aux_sender)) => {
(false, aux_sender)
},
_ => return Err("Invalid surcharge claim: origin must be signed or \
inherent and auxiliary sender only provided on inherent")
_ => return Err(
"Invalid surcharge claim: origin must be signed or \
inherent and auxiliary sender only provided on inherent"
),
};
// Add some advantage for block producers (who send unsigned extrinsics) by
@@ -619,7 +634,7 @@ decl_module! {
// If poking the contract has lead to eviction of the contract, give out the rewards.
if rent::try_evict::<T>(&dest, handicap) == rent::RentOutcome::Evicted {
T::Currency::deposit_into_existing(rewarded, T::SurchargeReward::get())?;
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
}
}
@@ -937,3 +952,62 @@ 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)
}
}
#[cfg(feature = "std")]
impl<T: Trait + Send + Sync> std::fmt::Debug for CheckBlockGasLimit<T> {
fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
Ok(())
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckBlockGasLimit<T> {
type AccountId = T::AccountId;
type Call = <T as Trait>::Call;
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(&self) -> rstd::result::Result<(), TransactionValidityError> { Ok(()) }
fn validate(
&self,
_: &Self::AccountId,
call: &Self::Call,
_: DispatchInfo,
_: 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::create(_, 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"),
}
}
}
+29 -10
View File
@@ -22,27 +22,27 @@
use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
use crate::{
BalanceOf, ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig,
Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter,
TrieIdGenerator, Schedule,
Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, Schedule,
TrieIdGenerator, CheckBlockGasLimit,
};
use assert_matches::assert_matches;
use hex_literal::*;
use codec::{Decode, Encode, KeyedVec};
use runtime_io;
use runtime_io::with_externalities;
use sr_primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, H256};
use sr_primitives::traits::{BlakeTwo256, Hash, IdentityLookup};
use sr_primitives::{Perbill, BuildStorage};
use sr_primitives::{
Perbill, BuildStorage, transaction_validity::{InvalidTransaction, ValidTransaction},
traits::{BlakeTwo256, Hash, IdentityLookup, SignedExtension},
weights::{DispatchInfo, DispatchClass},
testing::{Digest, DigestItem, Header, UintAuthorityId, H256},
};
use support::{
assert_ok, assert_err, impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types,
storage::child, StorageMap, StorageValue, traits::{Currency, Get},
};
use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
use primitives::storage::well_known_keys;
use primitives::Blake2Hasher;
use std::{cell::RefCell, sync::atomic::{AtomicUsize, Ordering}};
use primitives::{storage::well_known_keys, Blake2Hasher};
use system::{self, EventRecord, Phase};
use {balances, wabt};
mod contract {
// Re-export contents of the root. This basically
@@ -2390,3 +2390,22 @@ fn cannot_self_destruct_in_constructor() {
}
);
}
#[test]
fn check_block_gas_limit_works() {
with_externalities(
&mut ExtBuilder::default().block_gas_limit(50).build(),
|| {
let info = DispatchInfo { weight: 100, class: DispatchClass::Normal };
let check = CheckBlockGasLimit::<Test>(Default::default());
let call: Call = crate::Call::put_code(1000, vec![]).into();
assert_eq!(
check.validate(&0, &call, info, 0), InvalidTransaction::ExhaustsResources.into(),
);
let call: Call = crate::Call::update_schedule(Default::default()).into();
assert_eq!(check.validate(&0, &call, info, 0), Ok(Default::default()));
}
);
}