Improve Dispatch Errors (#878)

* better dispatch errors

* dry_run to use same DispatchError

* fix dry_run_fails; use correct transfer amount

* Hide ModuleError impl and avoid pulling details from metadata unless user needs them

* fix tests

* actually fix the tests (hopefully..)

* Add a couple more DispatchError test cases

* Add a comment about where the error was copied from

* Also expose a way to obtain the raw module error data

* Remove redundant variant prefixes

* explicit lifetime on From<str> for clarity

* fmt
This commit is contained in:
James Wilson
2023-03-23 09:50:44 +00:00
committed by GitHub
parent 7c252fccf7
commit b5194be5a2
14 changed files with 673 additions and 474 deletions
+2 -2
View File
@@ -444,10 +444,10 @@ impl<T: Config> Rpc<T> {
&self,
encoded_signed: &[u8],
at: Option<T::Hash>,
) -> Result<types::DryRunResult, Error> {
) -> Result<types::DryRunResultBytes, Error> {
let params = rpc_params![to_hex(encoded_signed), at];
let result_bytes: types::Bytes = self.client.request("system_dryRun", params).await?;
Ok(types::decode_dry_run_result(&mut &*result_bytes.0)?)
Ok(types::DryRunResultBytes(result_bytes.0))
}
/// Subscribe to `chainHead_unstable_follow` to obtain all reported blocks by the chain.
+33 -64
View File
@@ -4,7 +4,7 @@
//! Types sent to/from the Substrate RPC interface.
use crate::Config;
use crate::{metadata::Metadata, Config};
use codec::{Decode, Encode};
use primitive_types::U256;
use serde::{Deserialize, Serialize};
@@ -13,40 +13,42 @@ use std::collections::HashMap;
// Subscription types are returned from some calls, so expose it with the rest of the returned types.
pub use super::rpc_client::Subscription;
/// Signal what the result of doing a dry run of an extrinsic is.
pub type DryRunResult = Result<(), DryRunError>;
/// An error dry running an extrinsic.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum DryRunError {
/// The extrinsic will not be included in the block
#[derive(Debug, PartialEq, Eq)]
pub enum DryRunResult {
/// The transaction could be included in the block and executed.
Success,
/// The transaction could be included in the block, but the call failed to dispatch.
DispatchError(crate::error::DispatchError),
/// The transaction could not be included in the block.
TransactionValidityError,
/// The extrinsic will be included in the block, but the call failed to dispatch.
DispatchError,
}
/// dryRun returns an ApplyExtrinsicResult, which is basically a
/// `Result<Result<(), DispatchError>, TransactionValidityError>`. We want to convert this to
/// a [`DryRunResult`].
///
/// - if `Ok(inner)`, the transaction will be included in the block
/// - if `Ok(Ok(()))`, the transaction will be included and the call will be dispatched
/// successfully
/// - if `Ok(Err(e))`, the transaction will be included but there is some error dispatching
/// the call to the module.
///
/// The errors get a bit involved and have been known to change over time. At the moment
/// then, we will keep things simple here and just decode the Result portion (ie the initial bytes)
/// and ignore the rest.
pub(crate) fn decode_dry_run_result<I: codec::Input>(
input: &mut I,
) -> Result<DryRunResult, codec::Error> {
let res = match <Result<Result<(), ()>, ()>>::decode(input)? {
Ok(Ok(())) => Ok(()),
Ok(Err(())) => Err(DryRunError::DispatchError),
Err(()) => Err(DryRunError::TransactionValidityError),
};
Ok(res)
/// The bytes representing an error dry running an extrinsic.
pub struct DryRunResultBytes(pub Vec<u8>);
impl DryRunResultBytes {
/// Attempt to decode the error bytes into a [`DryRunResult`] using the provided [`Metadata`].
pub fn into_dry_run_result(self, metadata: &Metadata) -> Result<DryRunResult, crate::Error> {
// dryRun returns an ApplyExtrinsicResult, which is basically a
// `Result<Result<(), DispatchError>, TransactionValidityError>`.
let bytes = self.0;
if bytes[0] == 0 && bytes[1] == 0 {
// Ok(Ok(())); transaction is valid and executed ok
Ok(DryRunResult::Success)
} else if bytes[0] == 0 && bytes[1] == 1 {
// Ok(Err(dispatch_error)); transaction is valid but execution failed
let dispatch_error =
crate::error::DispatchError::decode_from(&bytes[2..], metadata.clone())?;
Ok(DryRunResult::DispatchError(dispatch_error))
} else if bytes[0] == 1 {
// Err(transaction_error); some transaction validity error (we ignore the details at the moment)
Ok(DryRunResult::TransactionValidityError)
} else {
// unable to decode the bytes; they aren't what we expect.
Err(crate::Error::Unknown(bytes))
}
}
}
/// A number type that can be serialized both as a number or a string that encodes a number in a
@@ -817,39 +819,6 @@ mod test {
assert_deser(r#"1000000000000"#, NumberOrHex::Number(1000000000000));
}
#[test]
fn dry_run_result_is_substrate_compatible() {
use sp_runtime::{
transaction_validity::{
InvalidTransaction as SpInvalidTransaction,
TransactionValidityError as SpTransactionValidityError,
},
ApplyExtrinsicResult as SpApplyExtrinsicResult, DispatchError as SpDispatchError,
};
let pairs = vec![
// All ok
(SpApplyExtrinsicResult::Ok(Ok(())), Ok(())),
// Some transaction error
(
SpApplyExtrinsicResult::Err(SpTransactionValidityError::Invalid(
SpInvalidTransaction::BadProof,
)),
Err(DryRunError::TransactionValidityError),
),
// Some dispatch error
(
SpApplyExtrinsicResult::Ok(Err(SpDispatchError::BadOrigin)),
Err(DryRunError::DispatchError),
),
];
for (actual, expected) in pairs {
let encoded = actual.encode();
assert_eq!(decode_dry_run_result(&mut &*encoded).unwrap(), expected);
}
}
#[test]
fn justification_is_substrate_compatible() {
use sp_runtime::Justification as SpJustification;