mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-24 20:35:41 +00:00
xcm: Improve debuggability (#2799)
Adds more logging to the XCM execution for better debugging.
This commit is contained in:
@@ -24,7 +24,7 @@ use frame_support::{
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use sp_core::defer;
|
||||
use sp_io::hashing::blake2_128;
|
||||
use sp_std::{marker::PhantomData, prelude::*};
|
||||
use sp_std::{fmt::Debug, marker::PhantomData, prelude::*};
|
||||
use sp_weights::Weight;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
@@ -199,10 +199,7 @@ impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Con
|
||||
let origin = origin.into();
|
||||
log::trace!(
|
||||
target: "xcm::execute_xcm_in_credit",
|
||||
"origin: {:?}, message: {:?}, weight_credit: {:?}",
|
||||
origin,
|
||||
message,
|
||||
weight_credit,
|
||||
"origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}",
|
||||
);
|
||||
let mut properties = Properties { weight_credit, message_id: None };
|
||||
if let Err(e) = Config::Barrier::should_execute(
|
||||
@@ -213,11 +210,8 @@ impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Con
|
||||
) {
|
||||
log::trace!(
|
||||
target: "xcm::execute_xcm_in_credit",
|
||||
"Barrier blocked execution! Error: {:?}. (origin: {:?}, message: {:?}, properties: {:?})",
|
||||
e,
|
||||
origin,
|
||||
message,
|
||||
properties,
|
||||
"Barrier blocked execution! Error: {e:?}. \
|
||||
(origin: {origin:?}, message: {message:?}, properties: {properties:?})",
|
||||
);
|
||||
return Outcome::Error(XcmError::Barrier)
|
||||
}
|
||||
@@ -228,7 +222,7 @@ impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Con
|
||||
|
||||
while !message.0.is_empty() {
|
||||
let result = vm.process(message);
|
||||
log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result);
|
||||
log::trace!(target: "xcm::execute_xcm_in_credit", "result: {result:?}");
|
||||
message = if let Err(error) = result {
|
||||
vm.total_surplus.saturating_accrue(error.weight);
|
||||
vm.error = Some((error.index, error.xcm_error));
|
||||
@@ -302,65 +296,6 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
|
||||
self.process(xcm)
|
||||
}
|
||||
|
||||
fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
|
||||
log::trace!(
|
||||
target: "xcm::process",
|
||||
"origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}",
|
||||
self.origin_ref(),
|
||||
self.total_surplus,
|
||||
self.total_refunded,
|
||||
self.error_handler_weight,
|
||||
);
|
||||
let mut result = Ok(());
|
||||
for (i, instr) in xcm.0.into_iter().enumerate() {
|
||||
match &mut result {
|
||||
r @ Ok(()) => {
|
||||
// Initialize the recursion count only the first time we hit this code in our
|
||||
// potential recursive execution.
|
||||
let inst_res = recursion_count::using_once(&mut 1, || {
|
||||
recursion_count::with(|count| {
|
||||
if *count > RECURSION_LIMIT {
|
||||
return Err(XcmError::ExceedsStackLimit)
|
||||
}
|
||||
*count = count.saturating_add(1);
|
||||
Ok(())
|
||||
})
|
||||
// This should always return `Some`, but let's play it safe.
|
||||
.unwrap_or(Ok(()))?;
|
||||
|
||||
// Ensure that we always decrement the counter whenever we finish processing
|
||||
// the instruction.
|
||||
defer! {
|
||||
recursion_count::with(|count| {
|
||||
*count = count.saturating_sub(1);
|
||||
});
|
||||
}
|
||||
|
||||
self.process_instruction(instr)
|
||||
});
|
||||
if let Err(e) = inst_res {
|
||||
log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e);
|
||||
*r = Err(ExecutorError {
|
||||
index: i as u32,
|
||||
xcm_error: e,
|
||||
weight: Weight::zero(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(ref mut error) =>
|
||||
if let Ok(x) = Config::Weigher::instr_weight(&instr) {
|
||||
error.weight.saturating_accrue(x)
|
||||
},
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Execute any final operations after having executed the XCM message.
|
||||
/// This includes refunding surplus weight, trapping extra holding funds, and returning any
|
||||
/// errors during execution.
|
||||
@@ -468,6 +403,154 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn take_fee(&mut self, fee: MultiAssets, reason: FeeReason) -> XcmResult {
|
||||
if Config::FeeManager::is_waived(self.origin_ref(), reason) {
|
||||
return Ok(())
|
||||
}
|
||||
log::trace!(
|
||||
target: "xcm::fees",
|
||||
"taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}",
|
||||
fee,
|
||||
self.origin_ref(),
|
||||
self.fees_mode,
|
||||
reason,
|
||||
);
|
||||
let paid = if self.fees_mode.jit_withdraw {
|
||||
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
|
||||
for asset in fee.inner() {
|
||||
Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?;
|
||||
}
|
||||
fee
|
||||
} else {
|
||||
self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into()
|
||||
};
|
||||
Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calculates what `local_querier` would be from the perspective of `destination`.
|
||||
fn to_querier(
|
||||
local_querier: Option<MultiLocation>,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<Option<MultiLocation>, XcmError> {
|
||||
Ok(match local_querier {
|
||||
None => None,
|
||||
Some(q) => Some(
|
||||
q.reanchored(&destination, Config::UniversalLocation::get())
|
||||
.map_err(|_| XcmError::ReanchorFailed)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a bare `QueryResponse` message containing `response` informed by the given `info`.
|
||||
///
|
||||
/// The `local_querier` argument is the querier (if any) specified from the *local* perspective.
|
||||
fn respond(
|
||||
&mut self,
|
||||
local_querier: Option<MultiLocation>,
|
||||
response: Response,
|
||||
info: QueryResponseInfo,
|
||||
fee_reason: FeeReason,
|
||||
) -> Result<XcmHash, XcmError> {
|
||||
let querier = Self::to_querier(local_querier, &info.destination)?;
|
||||
let QueryResponseInfo { destination, query_id, max_weight } = info;
|
||||
let instruction = QueryResponse { query_id, response, max_weight, querier };
|
||||
let message = Xcm(vec![instruction]);
|
||||
self.send(destination, message, fee_reason)
|
||||
}
|
||||
|
||||
fn try_reanchor(
|
||||
asset: MultiAsset,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<(MultiAsset, InteriorMultiLocation), XcmError> {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
let asset = asset
|
||||
.reanchored(&destination, reanchor_context)
|
||||
.map_err(|()| XcmError::ReanchorFailed)?;
|
||||
Ok((asset, reanchor_context))
|
||||
}
|
||||
|
||||
fn try_reanchor_multilocation(
|
||||
location: MultiLocation,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<(MultiLocation, InteriorMultiLocation), XcmError> {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
let location = location
|
||||
.reanchored(&destination, reanchor_context)
|
||||
.map_err(|_| XcmError::ReanchorFailed)?;
|
||||
Ok((location, reanchor_context))
|
||||
}
|
||||
|
||||
/// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`.
|
||||
fn reanchored(
|
||||
mut assets: Assets,
|
||||
dest: &MultiLocation,
|
||||
maybe_failed_bin: Option<&mut Assets>,
|
||||
) -> MultiAssets {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
assets.reanchor(dest, reanchor_context, maybe_failed_bin);
|
||||
assets.into_assets_iter().collect::<Vec<_>>().into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
|
||||
self.process(xcm)
|
||||
}
|
||||
|
||||
fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
|
||||
log::trace!(
|
||||
target: "xcm::process",
|
||||
"origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}",
|
||||
self.origin_ref(),
|
||||
self.total_surplus,
|
||||
self.total_refunded,
|
||||
self.error_handler_weight,
|
||||
);
|
||||
let mut result = Ok(());
|
||||
for (i, instr) in xcm.0.into_iter().enumerate() {
|
||||
match &mut result {
|
||||
r @ Ok(()) => {
|
||||
// Initialize the recursion count only the first time we hit this code in our
|
||||
// potential recursive execution.
|
||||
let inst_res = recursion_count::using_once(&mut 1, || {
|
||||
recursion_count::with(|count| {
|
||||
if *count > RECURSION_LIMIT {
|
||||
return Err(XcmError::ExceedsStackLimit)
|
||||
}
|
||||
*count = count.saturating_add(1);
|
||||
Ok(())
|
||||
})
|
||||
// This should always return `Some`, but let's play it safe.
|
||||
.unwrap_or(Ok(()))?;
|
||||
|
||||
// Ensure that we always decrement the counter whenever we finish processing
|
||||
// the instruction.
|
||||
defer! {
|
||||
recursion_count::with(|count| {
|
||||
*count = count.saturating_sub(1);
|
||||
});
|
||||
}
|
||||
|
||||
self.process_instruction(instr)
|
||||
});
|
||||
if let Err(e) = inst_res {
|
||||
log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e);
|
||||
*r = Err(ExecutorError {
|
||||
index: i as u32,
|
||||
xcm_error: e,
|
||||
weight: Weight::zero(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(ref mut error) =>
|
||||
if let Ok(x) = Config::Weigher::instr_weight(&instr) {
|
||||
error.weight.saturating_accrue(x)
|
||||
},
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
|
||||
fn process_instruction(
|
||||
&mut self,
|
||||
@@ -551,22 +634,81 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
},
|
||||
Transact { origin_kind, require_weight_at_most, mut call } => {
|
||||
// We assume that the Relay-chain is allowed to use transact on this parachain.
|
||||
let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?;
|
||||
let origin = *self.origin_ref().ok_or_else(|| {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"No origin provided",
|
||||
);
|
||||
|
||||
XcmError::BadOrigin
|
||||
})?;
|
||||
|
||||
// TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain
|
||||
let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;
|
||||
ensure!(Config::SafeCallFilter::contains(&message_call), XcmError::NoPermission);
|
||||
let message_call = call.take_decoded().map_err(|_| {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Failed to decode call",
|
||||
);
|
||||
|
||||
XcmError::FailedToDecode
|
||||
})?;
|
||||
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Processing call: {message_call:?}",
|
||||
);
|
||||
|
||||
if !Config::SafeCallFilter::contains(&message_call) {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Call filtered by `SafeCallFilter`",
|
||||
);
|
||||
|
||||
return Err(XcmError::NoPermission)
|
||||
}
|
||||
|
||||
let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind)
|
||||
.map_err(|_| XcmError::BadOrigin)?;
|
||||
.map_err(|_| {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Failed to convert origin {origin:?} and origin kind {origin_kind:?} to a local origin."
|
||||
);
|
||||
|
||||
XcmError::BadOrigin
|
||||
})?;
|
||||
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Dispatching with origin: {dispatch_origin:?}",
|
||||
);
|
||||
|
||||
let weight = message_call.get_dispatch_info().weight;
|
||||
ensure!(weight.all_lte(require_weight_at_most), XcmError::MaxWeightInvalid);
|
||||
|
||||
if !weight.all_lte(require_weight_at_most) {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Max {weight} bigger than require at most {require_weight_at_most}",
|
||||
);
|
||||
|
||||
return Err(XcmError::MaxWeightInvalid)
|
||||
}
|
||||
|
||||
let maybe_actual_weight =
|
||||
match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
|
||||
Ok(post_info) => {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Dispatch successful: {post_info:?}"
|
||||
);
|
||||
self.transact_status = MaybeErrorCode::Success;
|
||||
post_info.actual_weight
|
||||
},
|
||||
Err(error_and_info) => {
|
||||
log::trace!(
|
||||
target: "xcm::process_instruction::transact",
|
||||
"Dispatch failed {error_and_info:?}"
|
||||
);
|
||||
|
||||
self.transact_status = error_and_info.error.encode().into();
|
||||
error_and_info.post_info.actual_weight
|
||||
},
|
||||
@@ -946,93 +1088,4 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
HrmpChannelClosing { .. } => Err(XcmError::Unimplemented),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_fee(&mut self, fee: MultiAssets, reason: FeeReason) -> XcmResult {
|
||||
if Config::FeeManager::is_waived(self.origin_ref(), reason) {
|
||||
return Ok(())
|
||||
}
|
||||
log::trace!(
|
||||
target: "xcm::fees",
|
||||
"taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}",
|
||||
fee,
|
||||
self.origin_ref(),
|
||||
self.fees_mode,
|
||||
reason,
|
||||
);
|
||||
let paid = if self.fees_mode.jit_withdraw {
|
||||
let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
|
||||
for asset in fee.inner() {
|
||||
Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?;
|
||||
}
|
||||
fee
|
||||
} else {
|
||||
self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into()
|
||||
};
|
||||
Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calculates what `local_querier` would be from the perspective of `destination`.
|
||||
fn to_querier(
|
||||
local_querier: Option<MultiLocation>,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<Option<MultiLocation>, XcmError> {
|
||||
Ok(match local_querier {
|
||||
None => None,
|
||||
Some(q) => Some(
|
||||
q.reanchored(&destination, Config::UniversalLocation::get())
|
||||
.map_err(|_| XcmError::ReanchorFailed)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a bare `QueryResponse` message containing `response` informed by the given `info`.
|
||||
///
|
||||
/// The `local_querier` argument is the querier (if any) specified from the *local* perspective.
|
||||
fn respond(
|
||||
&mut self,
|
||||
local_querier: Option<MultiLocation>,
|
||||
response: Response,
|
||||
info: QueryResponseInfo,
|
||||
fee_reason: FeeReason,
|
||||
) -> Result<XcmHash, XcmError> {
|
||||
let querier = Self::to_querier(local_querier, &info.destination)?;
|
||||
let QueryResponseInfo { destination, query_id, max_weight } = info;
|
||||
let instruction = QueryResponse { query_id, response, max_weight, querier };
|
||||
let message = Xcm(vec![instruction]);
|
||||
self.send(destination, message, fee_reason)
|
||||
}
|
||||
|
||||
fn try_reanchor(
|
||||
asset: MultiAsset,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<(MultiAsset, InteriorMultiLocation), XcmError> {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
let asset = asset
|
||||
.reanchored(&destination, reanchor_context)
|
||||
.map_err(|()| XcmError::ReanchorFailed)?;
|
||||
Ok((asset, reanchor_context))
|
||||
}
|
||||
|
||||
fn try_reanchor_multilocation(
|
||||
location: MultiLocation,
|
||||
destination: &MultiLocation,
|
||||
) -> Result<(MultiLocation, InteriorMultiLocation), XcmError> {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
let location = location
|
||||
.reanchored(&destination, reanchor_context)
|
||||
.map_err(|_| XcmError::ReanchorFailed)?;
|
||||
Ok((location, reanchor_context))
|
||||
}
|
||||
|
||||
/// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`.
|
||||
fn reanchored(
|
||||
mut assets: Assets,
|
||||
dest: &MultiLocation,
|
||||
maybe_failed_bin: Option<&mut Assets>,
|
||||
) -> MultiAssets {
|
||||
let reanchor_context = Config::UniversalLocation::get();
|
||||
assets.reanchor(dest, reanchor_context, maybe_failed_bin);
|
||||
assets.into_assets_iter().collect::<Vec<_>>().into()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user