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
+66 -104
View File
@@ -59,13 +59,13 @@
//! # pub type Balances = u64;
//! # pub type AllModules = u64;
//! # pub enum Runtime {};
//! # use sr_primitives::transaction_validity::TransactionValidity;
//! # use sr_primitives::transaction_validity::{TransactionValidity, UnknownTransaction};
//! # use sr_primitives::traits::ValidateUnsigned;
//! # impl ValidateUnsigned for Runtime {
//! # type Call = ();
//! #
//! # fn validate_unsigned(_call: &Self::Call) -> TransactionValidity {
//! # TransactionValidity::Invalid(0)
//! # UnknownTransaction::NoUnsignedValidator.into()
//! # }
//! # }
//! /// Executive: handles dispatch to the various modules.
@@ -74,50 +74,17 @@
#![cfg_attr(not(feature = "std"), no_std)]
use rstd::prelude::*;
use rstd::marker::PhantomData;
use rstd::result;
use sr_primitives::{generic::Digest, traits::{
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize,
OnInitialize, NumberFor, Block as BlockT, OffchainWorker, ValidateUnsigned
}};
use support::Dispatchable;
use rstd::{prelude::*, marker::PhantomData};
use sr_primitives::{
generic::Digest, ApplyResult, weights::GetDispatchInfo,
traits::{
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize, OnInitialize,
NumberFor, Block as BlockT, OffchainWorker, ValidateUnsigned, Dispatchable
},
transaction_validity::TransactionValidity,
};
use codec::{Codec, Encode};
use system::{extrinsics_root, DigestOf};
use sr_primitives::{ApplyOutcome, ApplyError};
use sr_primitives::transaction_validity::TransactionValidity;
use sr_primitives::weights::GetDispatchInfo;
mod internal {
use sr_primitives::traits::DispatchError;
pub enum ApplyError {
BadSignature(&'static str),
Stale,
Future,
CantPay,
FullBlock,
}
pub enum ApplyOutcome {
Success,
Fail(&'static str),
}
impl From<DispatchError> for ApplyError {
fn from(d: DispatchError) -> Self {
match d {
DispatchError::Payment => ApplyError::CantPay,
DispatchError::Exhausted => ApplyError::FullBlock,
DispatchError::NoPermission => ApplyError::CantPay,
DispatchError::BadState => ApplyError::CantPay,
DispatchError::Stale => ApplyError::Stale,
DispatchError::Future => ApplyError::Future,
DispatchError::BadProof => ApplyError::BadSignature(""),
}
}
}
}
/// Trait that can be used to execute a block.
pub trait ExecuteBlock<Block: BlockT> {
@@ -239,30 +206,19 @@ where
/// Apply extrinsic outside of the block execution function.
/// This doesn't attempt to validate anything regarding the block, but it builds a list of uxt
/// hashes.
pub fn apply_extrinsic(uxt: Block::Extrinsic) -> result::Result<ApplyOutcome, ApplyError> {
pub fn apply_extrinsic(uxt: Block::Extrinsic) -> ApplyResult {
let encoded = uxt.encode();
let encoded_len = encoded.len();
match Self::apply_extrinsic_with_len(uxt, encoded_len, Some(encoded)) {
Ok(internal::ApplyOutcome::Success) => Ok(ApplyOutcome::Success),
Ok(internal::ApplyOutcome::Fail(_)) => Ok(ApplyOutcome::Fail),
Err(internal::ApplyError::CantPay) => Err(ApplyError::CantPay),
Err(internal::ApplyError::BadSignature(_)) => Err(ApplyError::BadSignature),
Err(internal::ApplyError::Stale) => Err(ApplyError::Stale),
Err(internal::ApplyError::Future) => Err(ApplyError::Future),
Err(internal::ApplyError::FullBlock) => Err(ApplyError::FullBlock),
}
Self::apply_extrinsic_with_len(uxt, encoded_len, Some(encoded))
}
/// Apply an extrinsic inside the block execution function.
fn apply_extrinsic_no_note(uxt: Block::Extrinsic) {
let l = uxt.encode().len();
match Self::apply_extrinsic_with_len(uxt, l, None) {
Ok(internal::ApplyOutcome::Success) => (),
Ok(internal::ApplyOutcome::Fail(e)) => runtime_io::print(e),
Err(internal::ApplyError::CantPay) => panic!("All extrinsics should have sender able to pay their fees"),
Err(internal::ApplyError::BadSignature(_)) => panic!("All extrinsics should be properly signed"),
Err(internal::ApplyError::Stale) | Err(internal::ApplyError::Future) => panic!("All extrinsics should have the correct nonce"),
Err(internal::ApplyError::FullBlock) => panic!("Extrinsics should not exceed block limit"),
Ok(Ok(())) => (),
Ok(Err(e)) => runtime_io::print(e),
Err(e) => { let err: &'static str = e.into(); panic!(err) },
}
}
@@ -271,9 +227,9 @@ where
uxt: Block::Extrinsic,
encoded_len: usize,
to_note: Option<Vec<u8>>,
) -> result::Result<internal::ApplyOutcome, internal::ApplyError> {
) -> ApplyResult {
// Verify that the signature is good.
let xt = uxt.check(&Default::default()).map_err(internal::ApplyError::BadSignature)?;
let xt = uxt.check(&Default::default())?;
// We don't need to make sure to `note_extrinsic` only after we know it's going to be
// executed to prevent it from leaking in storage since at this point, it will either
@@ -286,15 +242,11 @@ where
// Decode parameters and dispatch
let dispatch_info = xt.get_dispatch_info();
let r = Applyable::dispatch(xt, dispatch_info, encoded_len)
.map_err(internal::ApplyError::from)?;
let r = Applyable::apply(xt, dispatch_info, encoded_len)?;
<system::Module<System>>::note_applied_extrinsic(&r, encoded_len as u32);
r.map(|_| internal::ApplyOutcome::Success).or_else(|e| match e {
sr_primitives::BLOCK_FULL => Err(internal::ApplyError::FullBlock),
e => Ok(internal::ApplyOutcome::Fail(e))
})
Ok(r)
}
fn final_checks(header: &System::Header) {
@@ -324,21 +276,8 @@ where
///
/// Changes made to storage should be discarded.
pub fn validate_transaction(uxt: Block::Extrinsic) -> TransactionValidity {
// Note errors > 0 are from ApplyError
const UNKNOWN_ERROR: i8 = -127;
const INVALID_INDEX: i8 = -10;
let encoded_len = uxt.using_encoded(|d| d.len());
let xt = match uxt.check(&Default::default()) {
// Checks out. Carry on.
Ok(xt) => xt,
// An unknown account index implies that the transaction may yet become valid.
Err("invalid account index") => return TransactionValidity::Unknown(INVALID_INDEX),
// Technically a bad signature could also imply an out-of-date account index, but
// that's more of an edge case.
Err(sr_primitives::BAD_SIGNATURE) => return TransactionValidity::Invalid(ApplyError::BadSignature as i8),
Err(_) => return TransactionValidity::Invalid(UNKNOWN_ERROR),
};
let xt = uxt.check(&Default::default())?;
let dispatch_info = xt.get_dispatch_info();
xt.validate::<UnsignedValidator>(dispatch_info, encoded_len)
@@ -357,13 +296,15 @@ mod tests {
use balances::Call;
use runtime_io::with_externalities;
use primitives::{H256, Blake2Hasher};
use sr_primitives::generic::Era;
use sr_primitives::Perbill;
use sr_primitives::weights::Weight;
use sr_primitives::traits::{Header as HeaderT, BlakeTwo256, IdentityLookup, ConvertInto};
use sr_primitives::testing::{Digest, Header, Block};
use support::{impl_outer_event, impl_outer_origin, parameter_types};
use support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason};
use sr_primitives::{
generic::Era, Perbill, DispatchError, weights::Weight, testing::{Digest, Header, Block},
traits::{Header as HeaderT, BlakeTwo256, IdentityLookup, ConvertInto},
transaction_validity::{InvalidTransaction, UnknownTransaction}, ApplyError,
};
use support::{
impl_outer_event, impl_outer_origin, parameter_types,
traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason},
};
use system;
use hex_literal::hex;
@@ -432,8 +373,8 @@ mod tests {
fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
match call {
Call::set_balance(_, _, _) => TransactionValidity::Valid(Default::default()),
_ => TransactionValidity::Invalid(0),
Call::set_balance(_, _, _) => Ok(Default::default()),
_ => UnknownTransaction::NoUnsignedValidator.into(),
}
}
}
@@ -479,7 +420,7 @@ mod tests {
Digest::default(),
));
let r = Executive::apply_extrinsic(xt);
assert_eq!(r, Ok(ApplyOutcome::Success));
assert!(r.is_ok());
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 142 - 10 - weight);
assert_eq!(<balances::Module<Runtime>>::total_balance(&2), 69);
});
@@ -582,14 +523,19 @@ mod tests {
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
for nonce in 0..=num_to_exhaust_block {
let xt = sr_primitives::testing::TestXt(sign_extra(1, nonce.into(), 0), Call::transfer::<Runtime>(33, 0));
let xt = sr_primitives::testing::TestXt(
sign_extra(1, nonce.into(), 0), Call::transfer::<Runtime>(33, 0),
);
let res = Executive::apply_extrinsic(xt);
if nonce != num_to_exhaust_block {
assert_eq!(res.unwrap(), ApplyOutcome::Success);
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), encoded_len * (nonce + 1));
assert!(res.is_ok());
assert_eq!(
<system::Module<Runtime>>::all_extrinsics_weight(),
encoded_len * (nonce + 1),
);
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(nonce as u32 + 1));
} else {
assert_eq!(res, Err(ApplyError::FullBlock));
assert_eq!(res, Err(InvalidTransaction::ExhaustsResources.into()));
}
}
});
@@ -606,9 +552,9 @@ mod tests {
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
assert_eq!(Executive::apply_extrinsic(xt.clone()).unwrap(), ApplyOutcome::Success);
assert_eq!(Executive::apply_extrinsic(x1.clone()).unwrap(), ApplyOutcome::Success);
assert_eq!(Executive::apply_extrinsic(x2.clone()).unwrap(), ApplyOutcome::Success);
assert!(Executive::apply_extrinsic(xt.clone()).unwrap().is_ok());
assert!(Executive::apply_extrinsic(x1.clone()).unwrap().is_ok());
assert!(Executive::apply_extrinsic(x2.clone()).unwrap().is_ok());
// default weight for `TestXt` == encoded length.
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), (3 * len).into());
@@ -624,12 +570,18 @@ mod tests {
#[test]
fn validate_unsigned() {
let xt = sr_primitives::testing::TestXt(None, Call::set_balance(33, 69, 69));
let valid = TransactionValidity::Valid(Default::default());
let mut t = new_test_ext(1);
with_externalities(&mut t, || {
assert_eq!(Executive::validate_transaction(xt.clone()), valid);
assert_eq!(Executive::apply_extrinsic(xt), Ok(ApplyOutcome::Fail));
assert_eq!(Executive::validate_transaction(xt.clone()), Ok(Default::default()));
assert_eq!(
Executive::apply_extrinsic(xt),
Ok(
Err(
DispatchError { module: None, error: 0, message: Some("RequireRootOrigin") }
)
)
);
});
}
@@ -657,11 +609,21 @@ mod tests {
));
if lock == WithdrawReasons::except(WithdrawReason::TransactionPayment) {
assert_eq!(Executive::apply_extrinsic(xt).unwrap(), ApplyOutcome::Fail);
assert_eq!(
Executive::apply_extrinsic(xt).unwrap(),
Err(DispatchError {
module: None,
error: 0,
message: Some("account liquidity restrictions prevent withdrawal"),
}),
);
// but tx fee has been deducted. the transaction failed on transfer, not on fee.
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 111 - 10 - weight);
} else {
assert_eq!(Executive::apply_extrinsic(xt), Err(ApplyError::CantPay));
assert_eq!(
Executive::apply_extrinsic(xt),
Err(ApplyError::Validity(InvalidTransaction::Payment.into())),
);
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 111);
}
});