Allow runtime to return transaction validation error codes (#1534)

* Allow runtime to return more detailed transaction validation errors.

* Re-use ApplyError codes and update test-runtime.

* Fix pool tests.

* Revert using Compact for validity.
This commit is contained in:
Tomasz Drwięga
2019-01-29 19:06:05 +01:00
committed by Gav Wood
parent 473721f959
commit d2cfd7b9dc
9 changed files with 36 additions and 26 deletions
+2 -2
View File
@@ -49,10 +49,10 @@ use runtime_primitives::BuildStorage;
use state_machine::backend::Backend as StateBackend; use state_machine::backend::Backend as StateBackend;
use executor::RuntimeInfo; use executor::RuntimeInfo;
use state_machine::{CodeExecutor, DBValue, ExecutionStrategy}; use state_machine::{CodeExecutor, DBValue, ExecutionStrategy};
use utils::{Meta, db_err, meta_keys, open_database, read_db, block_id_to_lookup_key, read_meta}; use crate::utils::{Meta, db_err, meta_keys, open_database, read_db, block_id_to_lookup_key, read_meta};
use client::LeafSet; use client::LeafSet;
use state_db::StateDb; use state_db::StateDb;
use storage_cache::{CachingState, SharedCache, new_shared_cache}; use crate::storage_cache::{CachingState, SharedCache, new_shared_cache};
use log::{trace, debug, warn}; use log::{trace, debug, warn};
pub use state_db::PruningMode; pub use state_db::PruningMode;
@@ -33,7 +33,7 @@ pub type TransactionTag = Vec<u8>;
#[cfg_attr(feature = "std", derive(Debug))] #[cfg_attr(feature = "std", derive(Debug))]
pub enum TransactionValidity { pub enum TransactionValidity {
/// Transaction is invalid. Details are described by the error code. /// Transaction is invalid. Details are described by the error code.
Invalid, Invalid(i8),
/// Transaction is valid. /// Transaction is valid.
Valid { Valid {
/// Priority of the transaction. /// Priority of the transaction.
@@ -60,5 +60,5 @@ pub enum TransactionValidity {
longevity: TransactionLongevity, longevity: TransactionLongevity,
}, },
/// Transaction validity can't be determined. /// Transaction validity can't be determined.
Unknown, Unknown(i8),
} }
+4 -4
View File
@@ -107,17 +107,17 @@ pub fn execute_block(block: Block) {
/// This doesn't attempt to validate anything regarding the block. /// This doesn't attempt to validate anything regarding the block.
pub fn validate_transaction(utx: Extrinsic) -> TransactionValidity { pub fn validate_transaction(utx: Extrinsic) -> TransactionValidity {
if check_signature(&utx).is_err() { if check_signature(&utx).is_err() {
return TransactionValidity::Invalid; return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
} }
let tx = utx.transfer(); let tx = utx.transfer();
let nonce_key = tx.from.to_keyed_vec(NONCE_OF); let nonce_key = tx.from.to_keyed_vec(NONCE_OF);
let expected_nonce: u64 = storage::get_or(&nonce_key, 0); let expected_nonce: u64 = storage::get_or(&nonce_key, 0);
if tx.nonce < expected_nonce { if tx.nonce < expected_nonce {
return TransactionValidity::Invalid; return TransactionValidity::Invalid(ApplyError::Stale as i8);
} }
if tx.nonce > expected_nonce + 64 { if tx.nonce > expected_nonce + 64 {
return TransactionValidity::Unknown; return TransactionValidity::Unknown(ApplyError::Future as i8);
} }
let hash = |from: &AccountId, nonce: u64| { let hash = |from: &AccountId, nonce: u64| {
@@ -139,7 +139,7 @@ pub fn validate_transaction(utx: Extrinsic) -> TransactionValidity {
priority: tx.amount, priority: tx.amount,
requires, requires,
provides, provides,
longevity: 64 longevity: 64,
} }
} }
@@ -24,14 +24,14 @@ use error_chain::{
error_chain! { error_chain! {
errors { errors {
/// Transaction is not verifiable yet, but might be in the future. /// Transaction is not verifiable yet, but might be in the future.
UnknownTransactionValidity { UnknownTransactionValidity(e: i8) {
description("Runtime cannot determine validity of the transaction yet."), description("Runtime cannot determine validity of the transaction yet."),
display("Unkown Transaction Validity"), display("Unkown Transaction Validity. Error code: {}", e),
} }
/// Transaction is invalid /// Transaction is invalid
InvalidTransaction { InvalidTransaction(e: i8) {
description("Runtime check for the transaction failed."), description("Runtime check for the transaction failed."),
display("Invalid Transaction"), display("Invalid Transaction. Error Code: {}", e),
} }
/// The transaction is temporarily baned /// The transaction is temporarily baned
TemporarilyBanned { TemporarilyBanned {
@@ -118,12 +118,12 @@ impl<B: ChainApi> Pool<B> {
valid_till: block_number.as_().saturating_add(longevity), valid_till: block_number.as_().saturating_add(longevity),
}) })
}, },
TransactionValidity::Invalid => { TransactionValidity::Invalid(e) => {
bail!(error::Error::from(error::ErrorKind::InvalidTransaction)) bail!(error::Error::from(error::ErrorKind::InvalidTransaction(e)))
}, },
TransactionValidity::Unknown => { TransactionValidity::Unknown(e) => {
self.listener.write().invalid(&hash); self.listener.write().invalid(&hash);
bail!(error::Error::from(error::ErrorKind::UnknownTransactionValidity)) bail!(error::Error::from(error::ErrorKind::UnknownTransactionValidity(e)))
}, },
} }
}) })
@@ -247,7 +247,7 @@ impl<B: ChainApi> Pool<B> {
// Collect the hashes of transactions that now became invalid (meaning that they are succesfuly pruned). // Collect the hashes of transactions that now became invalid (meaning that they are succesfuly pruned).
let hashes = results.into_iter().enumerate().filter_map(|(idx, r)| match r.map_err(error::IntoPoolError::into_pool_error) { let hashes = results.into_iter().enumerate().filter_map(|(idx, r)| match r.map_err(error::IntoPoolError::into_pool_error) {
Err(Ok(err)) => match err.kind() { Err(Ok(err)) => match err.kind() {
error::ErrorKind::InvalidTransaction => Some(hashes[idx].clone()), error::ErrorKind::InvalidTransaction(_) => Some(hashes[idx].clone()),
_ => None, _ => None,
}, },
_ => None, _ => None,
@@ -413,7 +413,7 @@ mod tests {
let nonce = uxt.transfer().nonce; let nonce = uxt.transfer().nonce;
if nonce < block_number { if nonce < block_number {
Ok(TransactionValidity::Invalid) Ok(TransactionValidity::Invalid(0))
} else { } else {
Ok(TransactionValidity::Valid { Ok(TransactionValidity::Valid {
priority: 4, priority: 4,
+1 -1
View File
@@ -53,7 +53,7 @@ impl txpool::ChainApi for TestApi {
priority: 1, priority: 1,
requires, requires,
provides, provides,
longevity: 64 longevity: 64,
}) })
} }
+16 -6
View File
@@ -223,31 +223,37 @@ impl<
/// ///
/// Changes made to the storage should be discarded. /// Changes made to the storage should be discarded.
pub fn validate_transaction(uxt: Block::Extrinsic) -> TransactionValidity { pub fn validate_transaction(uxt: Block::Extrinsic) -> TransactionValidity {
// Note errors > 0 are from ApplyError
const UNKNOWN_ERROR: i8 = -127;
const MISSING_SENDER: i8 = -20;
const INVALID_INDEX: i8 = -10;
let encoded_len = uxt.encode().len(); let encoded_len = uxt.encode().len();
let xt = match uxt.check(&Default::default()) { let xt = match uxt.check(&Default::default()) {
// Checks out. Carry on. // Checks out. Carry on.
Ok(xt) => xt, Ok(xt) => xt,
// An unknown account index implies that the transaction may yet become valid. // An unknown account index implies that the transaction may yet become valid.
Err("invalid account index") => return TransactionValidity::Unknown, Err("invalid account index") => return TransactionValidity::Unknown(INVALID_INDEX),
// Technically a bad signature could also imply an out-of-date account index, but // Technically a bad signature could also imply an out-of-date account index, but
// that's more of an edge case. // that's more of an edge case.
Err(_) => return TransactionValidity::Invalid, Err("bad signature") => return TransactionValidity::Invalid(ApplyError::BadSignature as i8),
Err(_) => return TransactionValidity::Invalid(UNKNOWN_ERROR),
}; };
if let (Some(sender), Some(index)) = (xt.sender(), xt.index()) { if let (Some(sender), Some(index)) = (xt.sender(), xt.index()) {
// pay any fees. // pay any fees.
if Payment::make_payment(sender, encoded_len).is_err() { if Payment::make_payment(sender, encoded_len).is_err() {
return TransactionValidity::Invalid return TransactionValidity::Invalid(ApplyError::CantPay as i8)
} }
// check index // check index
let mut expected_index = <system::Module<System>>::account_nonce(sender); let mut expected_index = <system::Module<System>>::account_nonce(sender);
if index < &expected_index { if index < &expected_index {
return TransactionValidity::Invalid return TransactionValidity::Invalid(ApplyError::Stale as i8)
} }
if *index > expected_index + As::sa(256) { if *index > expected_index + As::sa(256) {
return TransactionValidity::Unknown return TransactionValidity::Unknown(ApplyError::Future as i8)
} }
let mut deps = Vec::new(); let mut deps = Vec::new();
@@ -263,7 +269,11 @@ impl<
longevity: TransactionLongevity::max_value(), longevity: TransactionLongevity::max_value(),
} }
} else { } else {
return TransactionValidity::Invalid return TransactionValidity::Invalid(if xt.sender().is_none() {
MISSING_SENDER
} else {
INVALID_INDEX
})
} }
} }
} }