#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
pub use crate::pallet::*;
use crate::{bids::Bids, state::*};
use codec::{Decode, Encode};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
ensure,
traits::{Currency, Get},
weights::Weight,
RuntimeDebug,
};
use frame_system::{
ensure_signed,
offchain::{SignedPayload, SigningTypes},
pallet_prelude::{BlockNumberFor, OriginFor},
};
use sp_core::H256;
use sp_runtime::{
traits::{CheckedAdd, Zero},
DispatchError, KeyTypeId,
};
use sp_std::{convert::TryInto, vec, vec::Vec};
pub use t3rn_types::{
bid::SFXBid,
fsx::FullSideEffect,
sfx::{ConfirmedSideEffect, HardenedSideEffect, SecurityLvl, SideEffect, SideEffectId},
};
pub use t3rn_primitives::{
account_manager::{AccountManager, Outcome, RequestCharge},
attesters::AttestersReadApi,
circuit::{XExecSignalId, XExecStepSideEffectId},
claimable::{BenefitSource, CircuitRole},
executors::Executors,
gateway::{GatewayABIConfig, HasherAlgo as HA},
portal::{HeightResult, Portal},
volatile::LocalState,
xdns::Xdns,
xtx::{Xtx, XtxId},
GatewayType, *,
};
use crate::{
machine::{Machine, *},
square_up::SquareUp,
};
pub use state::XExecSignal;
use t3rn_abi::{recode::Codec, sfx_abi::SFXAbi};
pub use t3rn_primitives::light_client::InclusionReceipt;
use t3rn_primitives::{
attesters::AttestersWriteApi,
circuit::{CircuitSubmitAPI, ReadSFX},
};
pub use t3rn_sdk_primitives::signal::{ExecutionSignal, SignalKind};
use t3rn_types::{fsx::TargetId, sfx::Sfx4bId};
#[cfg(test)]
pub mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod bids;
pub mod machine;
pub mod square_up;
pub mod state;
pub mod weights;
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"circ");
pub type SystemHashing<T> = <T as frame_system::Config>::Hashing;
reexport_currency_types!();
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::*,
traits::{
fungible::{Inspect, Mutate},
Get,
},
};
use frame_system::pallet_prelude::*;
#[derive(Clone, Eq, PartialEq, Default, Encode, Decode, Debug, TypeInfo)]
pub enum Status {
#[default]
Success,
FailedExecution,
DispatchFailed,
ExecutionLimitExceeded,
NotificationLimitExceeded,
SendTimeout,
DeliveryTimeout,
ExecutionTimeout,
}
pub type Data = Vec<u8>;
pub type AssetId = u32;
pub type Gas = u64;
pub type AccountId32 = sp_runtime::AccountId32;
pub type AccountId20 = sp_core::H160;
pub type Value32 = u32;
pub type Value64 = u64;
pub type Value128 = u128;
pub type Value256 = sp_core::U256;
pub type Value = Value128;
pub type ValueEvm = Value256;
#[derive(Debug, Clone, Eq, Default, PartialEq, Encode, Decode, TypeInfo)]
pub struct XbiResult {
pub status: Status,
pub output: Data,
pub witness: Data,
}
use sp_std::borrow::ToOwned;
use t3rn_primitives::{
attesters::AttestersWriteApi,
circuit::{
CircuitDLQ, CircuitSubmitAPI, LocalStateExecutionView, LocalTrigger, OnLocalTrigger,
ReadSFX,
},
portal::Portal,
xdns::Xdns,
SpeedMode,
};
use t3rn_types::{migrations::v13::FullSideEffectV13, sfx::Sfx4bId};
pub use crate::weights::WeightInfo;
pub type EscrowBalance<T> = BalanceOf<T>;
#[pallet::storage]
#[pallet::getter(fn storage_migrations_done)]
pub type StorageMigrations<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn get_gmp)]
pub type GMP<T> = StorageMap<_, Identity, H256, H256, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_sfx_2_xtx_links)]
pub type SFX2XTXLinksMap<T> =
StorageMap<_, Identity, SideEffectId<T>, XExecSignalId<T>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_active_timing_links)]
pub type PendingXtxTimeoutsMap<T> = StorageMap<
_,
Identity,
XExecSignalId<T>,
AdaptiveTimeout<BlockNumberFor<T>, [u8; 4]>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_pending_xtx_bids_timeouts)]
pub type PendingXtxBidsTimeoutsMap<T> =
StorageMap<_, Identity, XExecSignalId<T>, BlockNumberFor<T>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_finalized_xtx)]
pub type FinalizedXtx<T> =
StorageMap<_, Identity, XExecSignalId<T>, BlockNumberFor<T>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_x_exec_signals)]
pub type XExecSignals<T> = StorageMap<
_,
Identity,
XExecSignalId<T>,
XExecSignal<<T as frame_system::Config>::AccountId, BlockNumberFor<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_local_xtx_state)]
pub type LocalXtxStates<T> = StorageMap<_, Identity, XExecSignalId<T>, LocalState, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_full_side_effects)]
pub type FullSideEffects<T> = StorageMap<
_,
Identity,
XExecSignalId<T>,
Vec<
Vec<
FullSideEffect<
<T as frame_system::Config>::AccountId,
BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_dlq)]
pub type DLQ<T> = StorageMap<
_,
Identity,
XExecSignalId<T>,
(BlockNumberFor<T>, Vec<TargetId>, SpeedMode),
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_signal_queue)]
pub(crate) type SignalQueue<T: Config> = StorageValue<
_,
BoundedVec<(T::AccountId, ExecutionSignal<T::Hash>), T::SignalQueueDepth>,
ValueQuery,
>;
#[pallet::config]
pub trait Config: frame_system::Config {
#[pallet::constant]
type SelfAccountId: Get<Self::AccountId>;
#[pallet::constant]
type SelfGatewayId: Get<[u8; 4]>;
#[pallet::constant]
type SelfParaId: Get<u32>;
#[pallet::constant]
type XtxTimeoutDefault: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type XtxTimeoutCheckInterval: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type SFXBiddingPeriod: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type DeletionQueueLimit: Get<u32>;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: weights::WeightInfo;
type Balances: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
type Currency: Currency<Self::AccountId>;
type Xdns: Xdns<Self, BalanceOf<Self>>;
type Attesters: AttestersWriteApi<Self::AccountId, DispatchError>
+ AttestersReadApi<Self::AccountId, BalanceOf<Self>, BlockNumberFor<Self>>;
type Executors: Executors<Self, BalanceOf<Self>>;
type AccountManager: AccountManager<
Self::AccountId,
BalanceOf<Self>,
Self::Hash,
BlockNumberFor<Self>,
u32,
>;
type Portal: Portal<Self>;
#[pallet::constant]
type SignalQueueDepth: Get<u32>;
type TreasuryAccounts: TreasuryAccountProvider<Self::AccountId>;
}
#[pallet::pallet]
#[pallet::generate_store(pub (super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
Weight::zero()
}
fn on_finalize(_n: frame_system::pallet_prelude::BlockNumberFor<T>) {
}
fn offchain_worker(_n: frame_system::pallet_prelude::BlockNumberFor<T>) {}
fn on_runtime_upgrade() -> Weight {
let max_weight = T::DbWeight::get().reads_writes(10, 10);
const CURRENT_STORAGE_VERSION: u32 = 1;
StorageMigrations::<T>::try_mutate(|current_version| {
match *current_version {
0 => {
FullSideEffects::<T>::translate(
|_,
value: Vec<
Vec<
FullSideEffectV13<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
>| {
Some(
value
.into_iter()
.map(|v| v.into_iter().map(FullSideEffect::from).collect())
.collect(),
)
},
);
*current_version = CURRENT_STORAGE_VERSION;
Ok::<Weight, DispatchError>(max_weight)
},
_ => {
Ok::<Weight, DispatchError>(Weight::zero())
},
}
})
.unwrap_or(Weight::zero())
}
}
impl<T: Config> CircuitDLQ<T> for Pallet<T> {
fn process_dlq(n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
Self::process_dlq(n)
}
fn process_adaptive_xtx_timeout_queue(
n: frame_system::pallet_prelude::BlockNumberFor<T>,
verifier: &GatewayVendor,
) -> Weight {
Self::process_adaptive_xtx_timeout_queue(n, verifier)
}
}
impl<T: Config> ReadSFX<T::Hash, T::AccountId, BalanceOf<T>, BlockNumberFor<T>> for Pallet<T> {
fn get_fsx_of_xtx(xtx_id: T::Hash) -> Result<Vec<T::Hash>, DispatchError> {
let full_side_effects = FullSideEffects::<T>::get(xtx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
let fsx_ids: Vec<T::Hash> = full_side_effects
.iter()
.flat_map(|fsx_vec| {
fsx_vec.iter().enumerate().map(|(index, fsx)| {
fsx.input
.generate_id::<SystemHashing<T>>(xtx_id.as_ref(), index as u32)
})
})
.collect::<Vec<T::Hash>>();
Ok(fsx_ids)
}
fn get_fsx_status(fsx_id: T::Hash) -> Result<CircuitStatus, DispatchError> {
let xtx_id = SFX2XTXLinksMap::<T>::get(fsx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
Ok(Self::get_xtx_status(xtx_id)?.0)
}
fn recover_latest_submitted_xtx_id() -> Result<T::Hash, DispatchError> {
let xtx_id = Self::get_pending_xtx_ids()
.pop()
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
Ok(xtx_id)
}
fn get_fsx_executor(fsx_id: T::Hash) -> Result<Option<T::AccountId>, DispatchError> {
let xtx_id = SFX2XTXLinksMap::<T>::get(fsx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
let full_side_effects = FullSideEffects::<T>::get(xtx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
if full_side_effects.is_empty() {
return Err(Error::<T>::XtxNotFound.into())
}
for fsx_vec in full_side_effects {
for (index, fsx) in fsx_vec.iter().enumerate() {
if fsx
.input
.generate_id::<SystemHashing<T>>(xtx_id.as_ref(), index as u32)
== fsx_id
{
return Ok(fsx.input.enforce_executor.clone())
}
}
}
Ok(None)
}
fn get_fsx(
fsx_id: T::Hash,
) -> Result<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
DispatchError,
> {
let xtx_id = SFX2XTXLinksMap::<T>::get(fsx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
let full_side_effects = FullSideEffects::<T>::get(xtx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
if full_side_effects.is_empty() {
return Err(Error::<T>::FSXNotFoundById.into())
}
for fsx_step in &full_side_effects {
for (index, fsx) in fsx_step.iter().enumerate() {
if fsx
.input
.generate_id::<SystemHashing<T>>(xtx_id.as_ref(), index as u32)
== fsx_id
{
return Ok(fsx.clone())
}
}
}
Err(Error::<T>::FSXNotFoundById.into())
}
fn get_fsx_requester(fsx_id: T::Hash) -> Result<T::AccountId, DispatchError> {
let xtx_id = SFX2XTXLinksMap::<T>::get(fsx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
let xtx = XExecSignals::<T>::get(xtx_id)
.ok_or::<DispatchError>(Error::<T>::XtxNotFound.into())?;
Ok(xtx.requester)
}
fn get_pending_xtx_ids() -> Vec<T::Hash> {
XExecSignals::<T>::iter()
.filter(|(_, xtx)| xtx.status < CircuitStatus::Finished)
.map(|(xtx_id, _)| xtx_id)
.collect::<Vec<T::Hash>>()
}
fn get_pending_xtx_for(
for_executor: T::AccountId,
) -> Vec<(
T::Hash, Vec<SideEffect<T::AccountId, BalanceOf<T>>>, Vec<T::Hash>, )> {
let mut active_xtx = Vec::new();
for active_xtx_id in Self::get_pending_xtx_ids() {
let fsx_ids = Self::get_fsx_of_xtx(active_xtx_id).unwrap_or_default();
let mut executors_of_fsx = Vec::new();
let mut side_effects_of_executor = Vec::new();
for fsx_id in fsx_ids {
match Self::get_fsx(fsx_id.clone()) {
Ok(fsx) =>
if fsx.input.enforce_executor == Some(for_executor.clone()) {
side_effects_of_executor.push(fsx.input);
executors_of_fsx.push(fsx_id);
},
Err(_) => {},
}
}
if !side_effects_of_executor.is_empty() {
active_xtx.push((active_xtx_id, side_effects_of_executor, executors_of_fsx));
}
}
active_xtx
}
fn get_xtx_status(
xtx_id: T::Hash,
) -> Result<
(
CircuitStatus,
AdaptiveTimeout<frame_system::pallet_prelude::BlockNumberFor<T>, TargetId>,
),
DispatchError,
> {
XExecSignals::<T>::get(xtx_id)
.map(|xtx| (xtx.status, xtx.timeouts_at))
.ok_or(Error::<T>::XtxNotFound.into())
}
}
impl<T: Config> CircuitSubmitAPI<T, BalanceOf<T>> for Pallet<T> {
fn on_extrinsic_trigger(
origin: OriginFor<T>,
side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
speed_mode: SpeedMode,
preferred_security_level: SecurityLvl,
) -> DispatchResultWithPostInfo {
Self::on_extrinsic_trigger(origin, side_effects, speed_mode, preferred_security_level)
}
fn on_remote_origin_trigger(
origin: OriginFor<T>,
order_origin: T::AccountId,
side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
speed_mode: SpeedMode,
) -> DispatchResultWithPostInfo {
Self::on_remote_origin_trigger(origin, order_origin, side_effects, speed_mode)
}
fn store_gmp_payload(id: H256, payload: H256) -> bool {
if <GMP<T>>::contains_key(id) {
return false
}
<GMP<T>>::insert(id, payload);
true
}
fn bid(
origin: OriginFor<T>,
sfx_id: SideEffectId<T>,
amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
Self::bid_sfx(origin, sfx_id, amount)
}
fn get_gmp_payload(id: H256) -> Option<H256> {
<GMP<T>>::get(id)
}
fn verify_sfx_proof(
target: TargetId,
speed_mode: SpeedMode,
source: Option<ExecutionSource>,
encoded_proof: Vec<u8>,
) -> Result<InclusionReceipt<BlockNumberFor<T>>, DispatchError> {
<T as Config>::Portal::verify_event_inclusion(target, speed_mode, source, encoded_proof)
}
}
impl<T: Config> OnLocalTrigger<T, BalanceOf<T>> for Pallet<T> {
fn load_local_state(
origin: &OriginFor<T>,
maybe_xtx_id: Option<T::Hash>,
) -> Result<LocalStateExecutionView<T, BalanceOf<T>>, DispatchError> {
let requester = Self::authorize(origin.to_owned(), CircuitRole::ContractAuthor)?;
let local_ctx = match maybe_xtx_id {
Some(xtx_id) => Machine::<T>::load_xtx(xtx_id)?,
None => {
let mut local_ctx =
Machine::<T>::setup(&[], &requester, None, &SecurityLvl::Optimistic)?;
Machine::<T>::compile(&mut local_ctx, no_mangle, no_post_updates)?;
local_ctx
},
};
let hardened_side_effects = local_ctx
.full_side_effects
.iter()
.map(|step| {
step.iter()
.map(|fsx| {
let effect: HardenedSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
> = fsx.clone().try_into().map_err(|e| {
log::debug!(
target: "runtime::circuit",
"Error converting side effect to runtime: {:?}",
e
);
Error::<T>::FailedToHardenFullSideEffect
})?;
Ok(effect)
})
.collect::<Result<
Vec<
HardenedSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
Error<T>,
>>()
})
.collect::<Result<
Vec<
Vec<
HardenedSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
>,
Error<T>,
>>()?;
let local_state_execution_view = LocalStateExecutionView::<T, BalanceOf<T>>::new(
local_ctx.xtx_id,
local_ctx.local_state.clone(),
hardened_side_effects,
local_ctx.xtx.steps_cnt,
);
log::debug!(
target: "runtime::circuit",
"load_local_state with status: {:?}",
local_ctx.xtx.status
);
Ok(local_state_execution_view)
}
fn on_local_trigger(
origin: &OriginFor<T>,
trigger: LocalTrigger<T>,
) -> Result<LocalStateExecutionView<T, BalanceOf<T>>, DispatchError> {
log::debug!(
target: "runtime::circuit",
"Handling on_local_trigger xtx: {:?}, contract: {:?}, side_effects: {:?}",
trigger.maybe_xtx_id,
trigger.contract,
trigger.submitted_side_effects
);
let requester = Self::authorize(origin.to_owned(), CircuitRole::ContractAuthor)?;
let mut local_ctx = match trigger.maybe_xtx_id {
Some(xtx_id) => Machine::<T>::load_xtx(xtx_id)?,
None => {
let mut local_ctx =
Machine::<T>::setup(&[], &requester, None, &SecurityLvl::Optimistic)?;
Machine::<T>::compile(&mut local_ctx, no_mangle, no_post_updates)?;
local_ctx
},
};
let xtx_id = local_ctx.xtx_id;
log::debug!(
target: "runtime::circuit",
"submit_side_effects xtx state with status: {:?}",
local_ctx.xtx.status.clone()
);
Machine::<T>::compile(
&mut local_ctx,
|current_fsx, local_state, steps_cnt, status, _requester| {
match Self::exec_in_xtx_ctx(xtx_id, local_state, current_fsx, steps_cnt) {
Err(err) => {
if status == CircuitStatus::Ready {
return Ok(PrecompileResult::TryKill(Cause::IntentionalKill))
}
Err(err)
},
Ok(new_fsx) => Ok(PrecompileResult::TryUpdateFSX(new_fsx)),
}
},
|_status_change, local_ctx| {
let sfx_vec: Vec<SideEffect<T::AccountId, BalanceOf<T>>> = local_ctx
.full_side_effects
.iter()
.flatten()
.map(|fsx| Ok(fsx.input.clone()))
.collect::<Result<Vec<SideEffect<T::AccountId, BalanceOf<T>>>, Error<T>>>(
)?;
Self::emit_sfx(local_ctx.xtx_id, &requester, &sfx_vec);
Ok(())
},
)?;
Self::load_local_state(origin, Some(xtx_id))
}
fn on_signal(origin: &OriginFor<T>, signal: ExecutionSignal<T::Hash>) -> DispatchResult {
log::debug!(target: "runtime::circuit", "Handling on_signal {:?}", signal);
let requester = Self::authorize(origin.to_owned(), CircuitRole::ContractAuthor)?;
<SignalQueue<T>>::mutate(|q| {
q.try_push((requester, signal))
.map_err(|_| Error::<T>::SignalQueueFull)
})?;
Ok(())
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(<T as pallet::Config>::WeightInfo::on_local_trigger())]
pub fn on_local_trigger(origin: OriginFor<T>, trigger: Vec<u8>) -> DispatchResult {
let _execution_state_view =
<Self as OnLocalTrigger<T, BalanceOf<T>>>::on_local_trigger(
&origin,
LocalTrigger::<T>::decode(&mut &trigger[..])
.map_err(|_| Error::<T>::InsuranceBondNotRequired)?,
)?;
Ok(())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::on_local_trigger())]
pub fn on_xcm_trigger(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
Err(Error::<T>::NotImplemented.into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::on_local_trigger())]
pub fn on_remote_gateway_trigger(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
Err(Error::<T>::NotImplemented.into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::cancel_xtx())]
pub fn cancel_xtx(origin: OriginFor<T>, xtx_id: T::Hash) -> DispatchResultWithPostInfo {
let attempting_requester = Self::authorize(origin, CircuitRole::Requester)?;
Machine::<T>::compile(
&mut Machine::<T>::load_xtx(xtx_id)?,
|current_fsx, _local_state, _steps_cnt, status, requester| {
if attempting_requester != requester || status > CircuitStatus::PendingBidding {
return Err(Error::<T>::UnauthorizedCancellation)
}
if current_fsx.iter().any(|fsx| fsx.best_bid.is_some()) {
return Err(Error::<T>::UnauthorizedCancellation)
}
Ok(PrecompileResult::TryKill(Cause::IntentionalKill))
},
no_post_updates,
)?;
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::cancel_xtx())]
pub fn revert(origin: OriginFor<T>, xtx_id: T::Hash) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let _success =
Machine::<T>::revert(xtx_id, Cause::IntentionalKill, infallible_no_post_updates);
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::cancel_xtx())]
pub fn trigger_dlq(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
Self::process_dlq(<frame_system::Pallet<T>>::block_number());
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::on_extrinsic_trigger())]
pub fn on_remote_origin_trigger(
origin: OriginFor<T>,
order_origin: T::AccountId,
side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
speed_mode: SpeedMode,
) -> DispatchResultWithPostInfo {
let call_origin = Self::authorize(origin, CircuitRole::Executor)?;
let requester = match OrderOrigin::<T::AccountId>::new(&order_origin) {
OrderOrigin::Local(_) => return Err(Error::<T>::InvalidOrderOrigin.into()),
OrderOrigin::Remote(_) => order_origin.clone(),
};
let _local_ctx = Self::do_on_extrinsic_trigger(
requester,
side_effects,
speed_mode,
&SecurityLvl::Escrow,
Some(call_origin),
)?;
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::on_extrinsic_trigger())]
pub fn on_extrinsic_trigger(
origin: OriginFor<T>,
side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
speed_mode: SpeedMode,
preferred_security_level: SecurityLvl,
) -> DispatchResultWithPostInfo {
let requester = Self::authorize(origin, CircuitRole::Requester)?;
let _local_ctx = Self::do_on_extrinsic_trigger(
requester.clone(),
side_effects,
speed_mode,
&preferred_security_level,
None,
)?;
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::confirm_side_effect())]
pub fn escrow(origin: OriginFor<T>, sfx_id: SideEffectId<T>) -> DispatchResultWithPostInfo {
let executor = Self::authorize(origin, CircuitRole::Executor)?;
let xtx_id =
<Self as Store>::SFX2XTXLinksMap::get(sfx_id).ok_or(Error::<T>::XtxNotFound)?;
let mut local_ctx = Machine::<T>::load_xtx(xtx_id)?;
let fsx = local_ctx
.full_side_effects
.iter()
.flat_map(|fsx_vec| fsx_vec.iter())
.find(|fsx| {
fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id) == sfx_id
&& fsx.security_lvl == SecurityLvl::Escrow
})
.ok_or(Error::<T>::EscrowExecutionNotApplicableForThisSFX)?;
if fsx.input.enforce_executor != Some(executor.clone()) {
return Err(Error::<T>::EscrowExecutionNotApplicableForThisSFX.into())
}
let (escrow_asset, target_account, escrow_amount) = Self::recover_escrow_arguments(fsx)
.map_err(|e| {
log::error!("Self::recover_escrow_arguments hit an error -- {:?}", e);
Error::<T>::EscrowExecutionNotApplicableForThisSFX
})?;
let _escrow_account =
T::TreasuryAccounts::get_treasury_account(TreasuryAccount::Escrow);
let escrow_id = fsx
.input
.generate_id::<SystemHashing<T>>(sfx_id.as_ref(), 3333);
T::AccountManager::deposit(
escrow_id,
RequestCharge {
payee: executor.clone(),
offered_reward: escrow_amount,
charge_fee: Zero::zero(),
source: BenefitSource::EscrowUnlock,
role: CircuitRole::Requester,
recipient: Some(target_account),
maybe_asset_id: escrow_asset,
},
)?;
Machine::<T>::compile(
&mut local_ctx,
|_current_fsx, _local_state, _steps_cnt, _status, _requester| {
Ok(PrecompileResult::TryConfirm(
sfx_id,
ConfirmedSideEffect {
err: None,
output: None,
inclusion_data: vec![],
executioner: executor.clone(),
received_at: frame_system::Pallet::<T>::block_number(),
cost: None,
},
))
},
|status_change, local_ctx| {
Self::deposit_event(Event::SideEffectConfirmed(sfx_id));
if status_change.1 == CircuitStatus::FinishedAllSteps
|| status_change.1 == CircuitStatus::Committed
{
Self::request_sfx_attestation(local_ctx);
}
Self::emit_status_update(
local_ctx.xtx_id,
Some(local_ctx.xtx.clone()),
Some(local_ctx.full_side_effects.clone()),
);
Ok(())
},
)?;
let ddd_hotswap_result = Self::perform_ddd_hot_swap(&mut local_ctx, &executor)
.map_err(|e| {
log::error!("Self::perform_ddd_hot_swap hit an error -- {:?}", e);
Error::<T>::FailedToPerformDynamicDestinationDealHotSwap
})?;
Self::deposit_event(Event::SideEffectConfirmed(sfx_id));
for (xtx_id, sfx_id, executor, new_action_id) in ddd_hotswap_result {
Self::deposit_event(Event::DynamicDestinationDealReplaced(
xtx_id,
sfx_id,
executor,
new_action_id,
));
}
Ok(().into())
}
#[pallet::weight(<T as pallet::Config>::WeightInfo::bid_sfx())]
pub fn bid_sfx(
origin: OriginFor<T>,
sfx_id: SideEffectId<T>,
bid_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let bidder = Self::authorize(origin, CircuitRole::Executor)?;
let xtx_id = <Self as Store>::SFX2XTXLinksMap::get(sfx_id)
.ok_or(Error::<T>::LocalSideEffectExecutionNotApplicable)?;
Machine::<T>::compile(
&mut Machine::<T>::load_xtx(xtx_id)?,
|_current_fsx, _local_state, _steps_cnt, _status, _requester| {
Ok(PrecompileResult::TryBid((
sfx_id,
bid_amount,
bidder.clone(),
)))
},
|_status_change, _local_ctx| {
Self::deposit_event(Event::SFXNewBidReceived(
sfx_id,
bidder.clone(),
bid_amount,
));
Ok(())
},
)?;
Ok(().into())
}
#[pallet::weight(< T as Config >::WeightInfo::confirm_side_effect())]
pub fn confirm_side_effect(
origin: OriginFor<T>,
sfx_id: SideEffectId<T>,
confirmation: ConfirmedSideEffect<
<T as frame_system::Config>::AccountId,
BlockNumberFor<T>,
BalanceOf<T>,
>,
) -> DispatchResultWithPostInfo {
let _executor = Self::authorize(origin, CircuitRole::Executor)?;
let xtx_id = <Self as Store>::SFX2XTXLinksMap::get(sfx_id)
.ok_or(Error::<T>::LocalSideEffectExecutionNotApplicable)?;
Machine::<T>::compile(
&mut Machine::<T>::load_xtx(xtx_id)?,
|current_fsx, _local_state, _steps_cnt, __status, _requester| {
Self::confirm(xtx_id, current_fsx, &sfx_id, &confirmation).map_err(|e| {
log::error!("Self::confirm hit an error -- {:?}", e);
Error::<T>::ConfirmationFailed
})?;
Ok(PrecompileResult::TryConfirm(sfx_id, confirmation))
},
|status_change, local_ctx| {
Self::deposit_event(Event::SideEffectConfirmed(sfx_id));
if status_change.1 == CircuitStatus::FinishedAllSteps
|| status_change.1 == CircuitStatus::Committed
{
Self::request_sfx_attestation(local_ctx);
}
Self::emit_status_update(
local_ctx.xtx_id,
Some(local_ctx.xtx.clone()),
Some(local_ctx.full_side_effects.clone()),
);
Ok(())
},
)?;
Ok(().into())
}
}
use crate::machine::{no_mangle, Machine};
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
Transfer(T::AccountId, AccountId32, AccountId32, Value),
TransferAssets(T::AccountId, AssetId, AccountId32, AccountId32, Value),
TransferORML(T::AccountId, AssetId, AccountId32, AccountId32, Value),
AddLiquidity(T::AccountId, AssetId, AssetId, Value, Value, Value),
Swap(T::AccountId, AssetId, AssetId, Value, Value, Value),
CallNative(T::AccountId, Data),
CallEvm(
T::AccountId,
AccountId20,
AccountId20,
ValueEvm,
Data,
Gas,
ValueEvm,
Option<ValueEvm>,
Option<ValueEvm>,
Vec<(AccountId20, Vec<sp_core::H256>)>,
),
CallWasm(T::AccountId, AccountId32, Value, Gas, Option<Value>, Data),
CallCustom(
T::AccountId,
AccountId32,
AccountId32,
Value,
Data,
Gas,
Data,
),
Result(T::AccountId, AccountId32, XbiResult, Data, Data),
XTransactionReceivedForExec(XExecSignalId<T>),
SFXNewBidReceived(
SideEffectId<T>,
<T as frame_system::Config>::AccountId,
BalanceOf<T>,
),
SideEffectConfirmed(XExecSignalId<T>),
DynamicDestinationDealReplaced(XExecSignalId<T>, SideEffectId<T>, T::AccountId, Sfx4bId),
XTransactionReadyForExec(XExecSignalId<T>),
XTransactionStepFinishedExec(XExecSignalId<T>),
XTransactionXtxFinishedExecAllSteps(XExecSignalId<T>),
XTransactionFSXCommitted(XExecSignalId<T>),
XTransactionXtxCommitted(XExecSignalId<T>),
XTransactionXtxRevertedAfterTimeOut(XExecSignalId<T>),
XTransactionXtxDroppedAtBidding(XExecSignalId<T>),
NewSideEffectsAvailable(
<T as frame_system::Config>::AccountId,
XExecSignalId<T>,
Vec<SideEffect<<T as frame_system::Config>::AccountId, BalanceOf<T>>>,
Vec<SideEffectId<T>>,
),
CancelledSideEffects(
<T as frame_system::Config>::AccountId,
XtxId<T>,
Vec<SideEffect<<T as frame_system::Config>::AccountId, BalanceOf<T>>>,
),
SideEffectsConfirmed(
XtxId<T>,
Vec<
Vec<
FullSideEffect<
<T as frame_system::Config>::AccountId,
BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
>,
),
EscrowTransfer(
T::AccountId, T::AccountId, BalanceOf<T>, ),
SuccessfulFSXCommitAttestationRequest(H256),
UnsuccessfulFSXCommitAttestationRequest(H256),
SuccessfulFSXRevertAttestationRequest(H256),
UnsuccessfulFSXRevertAttestationRequest(H256),
}
#[pallet::error]
pub enum Error<T> {
UpdateAttemptDoubleRevert,
UpdateAttemptDoubleKill,
UpdateStateTransitionDisallowed,
UpdateForcedStateTransitionDisallowed,
UpdateXtxTriggeredWithUnexpectedStatus,
ConfirmationFailed,
InvalidOrderOrigin,
ApplyTriggeredWithUnexpectedStatus,
BidderNotEnoughBalance,
RequesterNotEnoughBalance,
AssetsFailedToWithdraw,
SanityAfterCreatingSFXDepositsFailed,
ContractXtxKilledRunOutOfFunds,
ChargingTransferFailed,
ChargingTransferFailedAtPendingExecution,
XtxChargeFailedRequesterBalanceTooLow,
XtxChargeBondDepositFailedCantAccessBid,
FinalizeSquareUpFailed,
CriticalStateSquareUpCalledToFinishWithoutFsxConfirmed,
RewardTransferFailed,
RefundTransferFailed,
SideEffectsValidationFailed,
InsuranceBondNotRequired,
BiddingInactive,
BiddingRejectedBidBelowDust,
BiddingRejectedBidTooHigh,
BiddingRejectedInsuranceTooLow,
BiddingRejectedBetterBidFound,
BiddingRejectedFailedToDepositBidderBond,
BiddingFailedExecutorsBalanceTooLowToReserve,
InsuranceBondAlreadyDeposited,
InvalidFTXStateEmptyBidForReadyXtx,
InvalidFTXStateEmptyConfirmationForFinishedXtx,
InvalidFTXStateUnassignedExecutorForReadySFX,
InvalidFTXStateIncorrectExecutorForReadySFX,
GatewayNotActive,
SetupFailed,
SetupFailedXtxNotFound,
SetupFailedXtxStorageArtifactsNotFound,
SetupFailedIncorrectXtxStatus,
SetupFailedDuplicatedXtx,
SetupFailedEmptyXtx,
SetupFailedXtxAlreadyFinished,
SetupFailedXtxWasDroppedAtBidding,
SetupFailedXtxReverted,
SetupFailedXtxRevertedTimeout,
XtxDoesNotExist,
InvalidFSXBidStateLocated,
EnactSideEffectsCanOnlyBeCalledWithMin1StepFinished,
FatalXtxTimeoutXtxIdNotMatched,
RelayEscrowedFailedNothingToConfirm,
FatalCommitSideEffectWithoutConfirmationAttempt,
FatalErroredCommitSideEffectConfirmationAttempt,
FatalErroredRevertSideEffectConfirmationAttempt,
FailedToHardenFullSideEffect,
ApplyFailed,
DeterminedForbiddenXtxStatus,
SideEffectIsAlreadyScheduledToExecuteOverXBI,
FSXNotFoundById,
XtxNotFound,
LocalSideEffectExecutionNotApplicable,
EscrowExecutionNotApplicableForThisSFX,
LocalExecutionUnauthorized,
OnLocalTriggerFailedToSetupXtx,
UnauthorizedCancellation,
FailedToConvertSFX2XBI,
FailedToCheckInOverXBI,
FailedToCreateXBIMetadataDueToWrongAccountConversion,
FailedToConvertXBIResult2SFXConfirmation,
FailedToEnterXBIPortal,
FailedToExitXBIPortal,
FailedToCommitFSX,
XBIExitFailedOnSFXConfirmation,
UnsupportedRole,
InvalidLocalTrigger,
SignalQueueFull,
ArithmeticErrorOverflow,
ArithmeticErrorUnderflow,
ArithmeticErrorDivisionByZero,
ABIOnSelectedTargetNotFoundForSubmittedSFX,
ForNowOnlySingleRewardAssetSupportedForMultiSFX,
TargetAppearsNotToBeActiveAndDoesntHaveFinalizedHeight,
SideEffectsValidationFailedAgainstABI,
XtxChargeFailedOnEscrowFee,
FailedToPerformDynamicDestinationDealHotSwap,
NotImplemented,
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct Payload<Public, BlockNumber> {
block_number: BlockNumber,
public: Public,
}
impl<T: SigningTypes> SignedPayload<T> for Payload<T::Public, BlockNumberFor<T>> {
fn public(&self) -> T::Public {
self.public.clone()
}
}
impl<T: Config> Pallet<T> {
fn emit_sfx(
xtx_id: XExecSignalId<T>,
subjected_account: &T::AccountId,
side_effects: &Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
) {
if !side_effects.is_empty() {
Self::deposit_event(Event::NewSideEffectsAvailable(
subjected_account.clone(),
xtx_id,
side_effects.to_vec(),
side_effects
.iter()
.enumerate()
.map(|(index, se)| {
se.generate_id::<SystemHashing<T>>(xtx_id.as_ref(), index as u32)
})
.collect::<Vec<SideEffectId<T>>>(),
));
Self::deposit_event(Event::XTransactionReceivedForExec(xtx_id));
}
}
fn emit_status_update(
xtx_id: XExecSignalId<T>,
maybe_xtx: Option<XExecSignal<T::AccountId, BlockNumberFor<T>>>,
maybe_full_side_effects: Option<
Vec<
Vec<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
>,
>,
) {
if let Some(xtx) = maybe_xtx {
match xtx.status {
CircuitStatus::PendingBidding =>
Self::deposit_event(Event::XTransactionReceivedForExec(xtx_id)),
CircuitStatus::Ready =>
Self::deposit_event(Event::XTransactionReadyForExec(xtx_id)),
CircuitStatus::Finished =>
Self::deposit_event(Event::XTransactionStepFinishedExec(xtx_id)),
CircuitStatus::FinishedAllSteps =>
Self::deposit_event(Event::XTransactionXtxFinishedExecAllSteps(xtx_id)),
CircuitStatus::Reverted(ref _cause) =>
Self::deposit_event(Event::XTransactionXtxRevertedAfterTimeOut(xtx_id)),
CircuitStatus::Committed =>
Self::deposit_event(Event::XTransactionXtxCommitted(xtx_id)),
CircuitStatus::Killed(ref _cause) =>
Self::deposit_event(Event::XTransactionXtxDroppedAtBidding(xtx_id)),
_ => {},
}
if xtx.status >= CircuitStatus::PendingExecution {
if let Some(full_side_effects) = maybe_full_side_effects {
Self::deposit_event(Event::SideEffectsConfirmed(xtx_id, full_side_effects));
}
}
}
}
fn do_on_extrinsic_trigger(
requester: T::AccountId,
side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>>,
speed_mode: SpeedMode,
preferred_security_level: &SecurityLvl,
maybe_call_origin: Option<T::AccountId>,
) -> Result<LocalXtxCtx<T, BalanceOf<T>>, Error<T>> {
let mut fresh_xtx = Machine::<T>::setup(
&side_effects,
&requester,
Some(T::Xdns::estimate_adaptive_timeout_on_slowest_target(
side_effects
.iter()
.map(|sfx| sfx.target)
.collect::<Vec<TargetId>>(),
&speed_mode,
T::XtxTimeoutDefault::get(),
)),
preferred_security_level,
)?;
fresh_xtx.xtx.set_speed_mode(speed_mode);
let call_origin = maybe_call_origin.clone().unwrap_or(requester.clone());
SquareUp::<T>::charge_finality_fee(&fresh_xtx, &call_origin)
.map_err(|_e| Error::<T>::XtxChargeFailedOnEscrowFee)?;
Machine::<T>::compile(
&mut fresh_xtx,
|_, _, _, _, _| Ok(PrecompileResult::TryRequest),
|_status_change, local_ctx| {
let _call_origin = maybe_call_origin.unwrap_or(requester.clone());
Self::emit_sfx(local_ctx.xtx_id, &requester, &side_effects);
Ok(())
},
)?;
#[cfg(feature = "test-skip-verification")]
frame_system::Pallet::<T>::inc_account_nonce(requester);
Ok(fresh_xtx)
}
fn authorize(
origin: OriginFor<T>,
role: CircuitRole,
) -> Result<T::AccountId, sp_runtime::traits::BadOrigin> {
match role {
CircuitRole::Requester | CircuitRole::ContractAuthor => ensure_signed(origin),
CircuitRole::Relayer => ensure_signed(origin),
CircuitRole::Executor => ensure_signed(origin),
_ => return Err(sp_runtime::traits::BadOrigin.into()),
}
}
fn validate(
side_effects: &[SideEffect<T::AccountId, BalanceOf<T>>],
local_ctx: &mut LocalXtxCtx<T, BalanceOf<T>>,
_preferred_security_lvl: &SecurityLvl,
) -> Result<(), Error<T>> {
let mut full_side_effects: Vec<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
> = vec![];
if side_effects.is_empty() {
local_ctx.full_side_effects = vec![vec![]];
return Ok(())
}
let all_targets = side_effects
.iter()
.map(|sfx| sfx.target)
.collect::<Vec<TargetId>>();
ensure!(
Self::ensure_all_gateways_are_active(all_targets),
Error::<T>::GatewayNotActive
);
for (index, sfx) in side_effects.iter().enumerate() {
let gateway_max_security_lvl =
<T as Config>::Xdns::get_gateway_max_security_lvl(&sfx.target);
let sfx_abi: SFXAbi = match <T as Config>::Xdns::get_sfx_abi(&sfx.target, sfx.action) {
Some(sfx_abi) => sfx_abi,
None => return Err(Error::<T>::ABIOnSelectedTargetNotFoundForSubmittedSFX),
};
sfx.validate(sfx_abi, &Codec::Scale).map_err(|e| {
log::error!("sfx.validate against ABI failed: {:?}", e);
Error::<T>::SideEffectsValidationFailedAgainstABI
})?;
let submission_target_height = match T::Portal::get_finalized_height(sfx.target)
.map_err(|_| Error::<T>::TargetAppearsNotToBeActiveAndDoesntHaveFinalizedHeight)?
{
HeightResult::Height(block_numer) => block_numer,
HeightResult::NotActive =>
return Err(Error::<T>::TargetAppearsNotToBeActiveAndDoesntHaveFinalizedHeight),
};
full_side_effects.push(FullSideEffect {
input: sfx.clone(),
confirmed: None,
security_lvl: gateway_max_security_lvl,
submission_target_height,
best_bid: None,
index: index as u32,
});
}
full_side_effects.sort_by(|a, b| a.index.partial_cmp(&b.index).unwrap());
local_ctx.full_side_effects = vec![full_side_effects];
Ok(())
}
fn confirm(
xtx_id: XExecSignalId<T>,
step_side_effects: &mut Vec<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
sfx_id: &SideEffectId<T>,
confirmation: &ConfirmedSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
) -> Result<(), DispatchError> {
if step_side_effects.is_empty() {
return Err(DispatchError::Other("Xtx has an empty single step."))
}
let all_targets = step_side_effects
.iter()
.map(|sfx| sfx.input.target)
.collect::<Vec<TargetId>>();
ensure!(
Self::ensure_all_gateways_are_active(all_targets),
Error::<T>::GatewayNotActive
);
let fsx_opt = step_side_effects
.iter_mut()
.find(|fsx| fsx.calc_sfx_id::<SystemHashing<T>, T>(xtx_id) == *sfx_id);
let fsx = match fsx_opt {
Some(fsx) if fsx.confirmed.is_none() => {
fsx.confirmed = Some(confirmation.clone());
fsx.clone()
},
Some(_) => return Err(DispatchError::Other("Side Effect already confirmed")),
None =>
return Err(DispatchError::Other(
"Unable to find matching Side Effect in given Xtx to confirm",
)),
};
log::debug!("Order confirmed!");
#[cfg(not(feature = "test-skip-verification"))]
let xtx = Machine::<T>::load_xtx(xtx_id)?.xtx;
#[cfg(not(feature = "test-skip-verification"))]
let inclusion_receipt = <T as Config>::Portal::verify_event_inclusion(
fsx.input.target,
xtx.speed_mode,
None, confirmation.inclusion_data.clone(),
)
.map_err(|_| DispatchError::Other("SideEffect confirmation of inclusion failed"))?;
#[cfg(not(feature = "test-skip-verification"))]
log::debug!(
"SFX confirmation inclusion receipt: {:?}",
inclusion_receipt
);
log::debug!("Inclusion confirmed!");
let sfx_abi =
<T as Config>::Xdns::get_sfx_abi(&fsx.input.target, fsx.input.action).ok_or({
DispatchError::Other("Unable to find matching Side Effect descriptor in XDNS")
})?;
#[cfg(feature = "test-skip-verification")]
let inclusion_receipt = InclusionReceipt::<BlockNumberFor<T>> {
message: confirmation.inclusion_data.clone(),
including_header: [0u8; 32].encode(),
height: frame_system::pallet_prelude::BlockNumberFor::<T>::zero(),
}; #[cfg(not(feature = "test-skip-verification"))]
if inclusion_receipt.height < fsx.submission_target_height {
log::error!(
"Inclusion height is higher than target {:?} < {:?}. Xtx Id: {:?}",
inclusion_receipt.height,
fsx.submission_target_height,
xtx_id,
);
return Err(DispatchError::Other(
"SideEffect confirmation of inclusion failed - inclusion height is higher than target",
))
}
let payload_codec = <T as Config>::Xdns::get_target_codec(&fsx.input.target)?;
fsx.input.confirm(
sfx_abi,
inclusion_receipt.message,
&Codec::Scale, &payload_codec,
)?;
log::debug!("Confirmation success");
Ok(())
}
pub fn get_all_xtx_targets(xtx_id: XExecSignalId<T>) -> Vec<TargetId> {
let fsx_of_xtx = match <Pallet<T>>::get_fsx_of_xtx(xtx_id) {
Ok(fsx) => fsx,
Err(_) => return vec![],
};
fsx_of_xtx
.into_iter()
.filter_map(|fsx_id| {
<Pallet<T>>::get_fsx(fsx_id)
.ok()
.map(|fsx| fsx.input.target)
})
.collect()
}
pub fn ensure_all_gateways_are_active(targets: Vec<TargetId>) -> bool {
for target in targets.into_iter() {
let gateway_activity_overview = match <T as Config>::Xdns::read_last_activity(target) {
Some(gateway_activity_overview) => gateway_activity_overview,
None => {
log::warn!("Failing to ensure_all_gateways_are_active. Target gateway is not registered in XDNS. Observe XDNS::gateway_activity_overview for more updates");
return false
},
};
if !gateway_activity_overview.is_active {
log::warn!(
"Failing to ensure_all_gateways_are_active. Target gateway is currently not active. Observe XDNS::gateway_activity_overview for more updates"
);
return false
}
}
true
}
pub fn exec_in_xtx_ctx(
_xtx_id: T::Hash,
_local_state: LocalState,
_full_side_effects: &mut Vec<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
_steps_cnt: (u32, u32),
) -> Result<
Vec<
FullSideEffect<
T::AccountId,
frame_system::pallet_prelude::BlockNumberFor<T>,
BalanceOf<T>,
>,
>,
Error<T>,
> {
Ok(vec![])
}
pub fn account_id() -> T::AccountId {
<T as Config>::SelfAccountId::get()
}
pub fn get_pending_sfx_bids(
xtx_id: T::Hash,
sfx_id: T::Hash,
) -> Result<Option<SFXBid<T::AccountId, BalanceOf<T>, u32>>, Error<T>> {
let local_ctx = Machine::<T>::load_xtx(xtx_id)?;
let current_step_fsx = Machine::<T>::read_current_step_fsx(&local_ctx);
let fsx = current_step_fsx
.iter()
.find(|fsx| fsx.calc_sfx_id::<SystemHashing<T>, T>(xtx_id) == sfx_id)
.ok_or(Error::<T>::FSXNotFoundById)?;
match &fsx.best_bid {
Some(bid) => match &fsx.input.enforce_executor {
Some(_executor) => Ok(None),
None => Ok(Some(bid.clone())),
},
None => Ok(None),
}
}
pub fn convert_side_effects(
side_effects: Vec<Vec<u8>>,
) -> Result<Vec<SideEffect<T::AccountId, BalanceOf<T>>>, &'static str> {
let side_effects: Vec<SideEffect<T::AccountId, BalanceOf<T>>> = side_effects
.into_iter()
.filter_map(|se| se.try_into().ok())
.collect();
if side_effects.is_empty() {
Err("No side effects provided")
} else {
Ok(side_effects)
}
}
pub fn process_xtx_tick_queue(
n: frame_system::pallet_prelude::BlockNumberFor<T>,
kill_interval: frame_system::pallet_prelude::BlockNumberFor<T>,
max_allowed_weight: Weight,
) -> Weight {
let mut current_weight = 0;
if kill_interval == frame_system::pallet_prelude::BlockNumberFor::<T>::zero() {
return Weight::zero()
} else if n % kill_interval == frame_system::pallet_prelude::BlockNumberFor::<T>::zero() {
let _processed_xtx_revert_count = <PendingXtxBidsTimeoutsMap<T>>::iter()
.filter(|(_xtx_id, timeout_at)| timeout_at <= &n)
.map(|(xtx_id, _timeout_at)| {
if current_weight <= max_allowed_weight.ref_time() {
current_weight = current_weight
.saturating_add(Self::process_tick_one(xtx_id).ref_time());
}
})
.count();
let _processed_xtx_commit_count = <FinalizedXtx<T>>::iter()
.map(|(xtx_id, _)| {
if current_weight <= max_allowed_weight.ref_time() {
current_weight = current_weight
.saturating_add(Self::process_tick_two(xtx_id).ref_time());
}
})
.count();
}
Weight::from_parts(current_weight, 0u64)
}
pub fn process_adaptive_xtx_timeout_queue(
n: frame_system::pallet_prelude::BlockNumberFor<T>,
_verifier: &GatewayVendor,
) -> Weight {
let mut current_weight: Weight = Zero::zero();
let _processed_xtx_revert_count = <PendingXtxTimeoutsMap<T>>::iter()
.filter(|(_xtx_id, adaptive_timeout)| {
adaptive_timeout.estimated_height_here < n
})
.map(|(xtx_id, _timeout_at)| {
current_weight = current_weight.saturating_add(Self::process_revert_one(xtx_id).0);
})
.count();
current_weight
}
pub fn process_emergency_revert_xtx_queue(
n: frame_system::pallet_prelude::BlockNumberFor<T>,
revert_interval: frame_system::pallet_prelude::BlockNumberFor<T>,
max_allowed_weight: Weight,
) -> Weight {
let mut current_weight: Weight = Default::default();
if revert_interval == frame_system::pallet_prelude::BlockNumberFor::<T>::zero() {
return current_weight
} else if n % revert_interval == frame_system::pallet_prelude::BlockNumberFor::<T>::zero() {
let _processed_xtx_revert_count = <PendingXtxTimeoutsMap<T>>::iter()
.filter(|(_xtx_id, adaptive_timeout)| adaptive_timeout.emergency_timeout_here <= n)
.map(|(xtx_id, _timeout_at)| {
if current_weight.ref_time() <= max_allowed_weight.ref_time() {
current_weight =
current_weight.saturating_add(Self::process_revert_one(xtx_id).0);
}
})
.count();
}
current_weight
}
pub fn get_adaptive_timeout(
xtx_id: T::Hash,
maybe_speed_mode: Option<SpeedMode>,
) -> AdaptiveTimeout<frame_system::pallet_prelude::BlockNumberFor<T>, TargetId> {
let all_targets = Self::get_all_xtx_targets(xtx_id);
T::Xdns::estimate_adaptive_timeout_on_slowest_target(
all_targets,
&maybe_speed_mode.unwrap_or(SpeedMode::Finalized),
T::XtxTimeoutDefault::get(),
)
}
pub fn add_xtx_to_dlq(
xtx_id: T::Hash,
targets: Vec<TargetId>,
speed_mode: SpeedMode,
) -> (Weight, bool) {
if <DLQ<T>>::contains_key(xtx_id) {
return (T::DbWeight::get().reads(1), false)
}
<DLQ<T>>::insert(
xtx_id,
(
<frame_system::Pallet<T>>::block_number(),
targets,
speed_mode,
),
);
<XExecSignals<T>>::mutate(xtx_id, |xtx| {
if let Some(xtx) = xtx {
xtx.timeouts_at.dlq = Some(<frame_system::Pallet<T>>::block_number());
} else {
log::error!(
"Xtx not found in XExecSignals for xtx_id when add_xtx_to_dlq: {:?}",
xtx_id
)
}
});
if <PendingXtxTimeoutsMap<T>>::contains_key(xtx_id) {
<PendingXtxTimeoutsMap<T>>::remove(xtx_id);
}
(
T::DbWeight::get().reads_writes(2, 3), true,
)
}
pub fn remove_xtx_from_dlq(xtx_id: T::Hash) -> (Weight, bool) {
let dlq_entry = match <DLQ<T>>::take(xtx_id) {
Some(dlq_entry) => dlq_entry,
None => return (T::DbWeight::get().reads(1), false),
};
let adaptive_timeout = Self::get_adaptive_timeout(xtx_id, Some(dlq_entry.2));
<PendingXtxTimeoutsMap<T>>::insert(xtx_id, &adaptive_timeout);
<XExecSignals<T>>::mutate(xtx_id, |xtx| {
if let Some(xtx) = xtx {
xtx.timeouts_at = adaptive_timeout;
} else {
log::error!(
"Xtx not found in XExecSignals for xtx_id when remove_xtx_from_dlq: {:?}",
xtx_id
)
}
});
(
T::DbWeight::get().reads_writes(2, 3), true,
)
}
pub fn process_dlq(_n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
<DLQ<T>>::iter()
.map(|(xtx_id, (_block_number, targets, _speed_mode))| {
if Self::ensure_all_gateways_are_active(targets) {
Self::remove_xtx_from_dlq(xtx_id).0
} else {
T::DbWeight::get().reads(1)
}
})
.reduce(|a, b| a.saturating_add(b))
.unwrap_or_else(|| T::DbWeight::get().reads(1))
}
pub fn process_revert_one(xtx_id: XExecSignalId<T>) -> (Weight, bool) {
const REVERT_WRITES: u64 = 2;
const REVERT_READS: u64 = 1;
let all_targets = Self::get_all_xtx_targets(xtx_id);
if !Self::ensure_all_gateways_are_active(all_targets.clone()) {
return Self::add_xtx_to_dlq(xtx_id, all_targets, SpeedMode::Finalized)
}
let success: bool =
Machine::<T>::revert(xtx_id, Cause::Timeout, |_status_change, local_ctx| {
Self::request_sfx_attestation(local_ctx);
Self::deposit_event(Event::XTransactionXtxRevertedAfterTimeOut(xtx_id));
});
(
T::DbWeight::get().reads_writes(REVERT_READS, REVERT_WRITES),
success,
)
}
pub fn request_sfx_attestation(local_ctx: &LocalXtxCtx<T, BalanceOf<T>>) {
Machine::<T>::read_current_step_fsx(local_ctx)
.iter()
.for_each(|fsx| {
if fsx.security_lvl == SecurityLvl::Escrow {
let sfx_id: H256 = H256::from_slice(
fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id)
.as_ref(),
);
match local_ctx.xtx.status {
CircuitStatus::Reverted(_) =>
match T::Attesters::request_sfx_attestation_revert(
fsx.input.target,
sfx_id,
) {
Ok(_) => {
Self::deposit_event(
Event::SuccessfulFSXRevertAttestationRequest(sfx_id),
);
},
Err(_) => {
Self::deposit_event(
Event::UnsuccessfulFSXRevertAttestationRequest(sfx_id),
);
},
},
CircuitStatus::FinishedAllSteps | CircuitStatus::Committed =>
match T::Attesters::request_sfx_attestation_commit(
fsx.input.target,
sfx_id,
<Self as CircuitSubmitAPI<T, BalanceOf<T>>>::get_gmp_payload(
sfx_id,
),
) {
Ok(_) => {
Self::deposit_event(
Event::SuccessfulFSXCommitAttestationRequest(sfx_id),
);
},
Err(_) => {
Self::deposit_event(
Event::UnsuccessfulFSXCommitAttestationRequest(sfx_id),
);
},
},
_ => {},
}
}
});
}
pub fn process_tick_one(xtx_id: XExecSignalId<T>) -> Weight {
const KILL_WRITES: u64 = 4;
const KILL_READS: u64 = 1;
Machine::<T>::compile_infallible(
&mut Machine::<T>::load_xtx(xtx_id).expect("xtx_id corresponds to a valid Xtx when reading from PendingXtxBidsTimeoutsMap storage"),
|current_fsx, _local_state, _steps_cnt, status, _requester| {
match status {
CircuitStatus::InBidding => match current_fsx.iter().all(|fsx| fsx.best_bid.is_some()) {
true => PrecompileResult::ForceUpdateStatus(CircuitStatus::Ready),
false => PrecompileResult::TryKill(Cause::Timeout)
},
_ => PrecompileResult::TryKill(Cause::Timeout)
}
},
|_status_change, local_ctx| {
Self::emit_status_update(
local_ctx.xtx_id,
Some(local_ctx.xtx.clone()),
None,
);
},
);
T::DbWeight::get().reads_writes(KILL_READS, KILL_WRITES)
}
pub fn process_tick_two(xtx_id: XExecSignalId<T>) -> Weight {
const KILL_WRITES: u64 = 4;
const KILL_READS: u64 = 1;
let mut xtx_context = match Machine::<T>::load_xtx(xtx_id) {
Ok(ctx) => ctx,
Err(_) => return T::DbWeight::get().reads_writes(KILL_READS, 0),
};
Machine::<T>::compile_infallible(
&mut xtx_context,
|_current_fsx, _local_state, _steps_cnt, status, _requester| match status {
CircuitStatus::FinishedAllSteps =>
PrecompileResult::ForceUpdateStatus(CircuitStatus::Committed),
_ => PrecompileResult::TryKill(Cause::Timeout),
},
|_status_change, local_ctx| {
Self::emit_status_update(local_ctx.xtx_id, Some(local_ctx.xtx.clone()), None);
},
);
T::DbWeight::get().reads_writes(KILL_READS, KILL_WRITES)
}
pub fn process_signal_queue(
_n: BlockNumberFor<T>,
_interval: BlockNumberFor<T>,
max_allowed_weight: Weight,
) -> Weight {
let queue_len = <SignalQueue<T>>::decode_len().unwrap_or(0);
if queue_len == 0 {
return Weight::zero()
}
let db_weight = T::DbWeight::get();
let mut queue = <SignalQueue<T>>::get();
let mut processed_weight = 0;
while !queue.is_empty() && processed_weight < max_allowed_weight.ref_time() {
let (_requester, signal) = &mut queue.swap_remove(0);
if let Some(v) = processed_weight.checked_add(db_weight.reads(4).ref_time()) {
processed_weight = v
}
match Machine::<T>::load_xtx(signal.execution_id) {
Ok(local_ctx) => {
let _success: bool = Machine::<T>::kill(
local_ctx.xtx_id,
Cause::IntentionalKill,
|_status_change, _local_ctx| {
if let Some(v) = processed_weight
.checked_add(db_weight.reads_writes(2, 1).ref_time())
{
processed_weight = v
}
},
);
},
Err(err) => match err {
Error::XtxDoesNotExist => {
log::error!(
"Failed to process signal is for non-existent Xtx: {:?}. Removing from queue.",
signal.execution_id
);
},
_ => {
log::error!("Failed to process signal ID {:?} with Err: {:?}. Sliding back to queue.", signal.execution_id, err);
queue.slide(0, queue.len());
},
},
}
}
if let Some(v) = processed_weight.checked_add(db_weight.reads_writes(1, 1).ref_time()) {
processed_weight = v
} else {
log::error!("Could not initial read of queue and update")
}
<SignalQueue<T>>::put(queue);
Weight::from_parts(processed_weight, 0u64)
}
pub fn recover_local_ctx_by_sfx_id(
sfx_id: SideEffectId<T>,
) -> Result<LocalXtxCtx<T, BalanceOf<T>>, Error<T>> {
let xtx_id = <Self as Store>::SFX2XTXLinksMap::get(sfx_id)
.ok_or(Error::<T>::LocalSideEffectExecutionNotApplicable)?;
Machine::<T>::load_xtx(xtx_id)
}
fn recover_escrow_arguments(
fsx: &FullSideEffect<T::AccountId, BlockNumberFor<T>, BalanceOf<T>>,
) -> Result<(Option<AssetId>, T::AccountId, BalanceOf<T>), DispatchError> {
match &fsx.input.action {
b"tran" => {
let target_account_bytes = fsx.input.encoded_args.get(0).ok_or::<DispatchError>(
"RecoverEscrowArgumentsFailedToAccessTargetAccountFromField0".into(),
)?;
let target_account = T::AccountId::decode(&mut &target_account_bytes[..])
.map_err(|_e| "RecoverEscrowArgumentsFailedOnDecodingArguments")?;
let escrow_amount_bytes = fsx.input.encoded_args.get(1).ok_or::<DispatchError>(
"RecoverEscrowArgumentsFailedToAccessAmountFromField1".into(),
)?;
let escrow_amount = BalanceOf::<T>::decode(&mut &escrow_amount_bytes[..])
.map_err(|_e| "RecoverEscrowArgumentsFailedOnDecodingArguments")?;
Ok((None, target_account, escrow_amount))
},
b"tass" => {
let asset_bytes = fsx.input.encoded_args.get(0).ok_or::<DispatchError>(
"RecoverEscrowArgumentsFailedToAccessAssetIdFromField0".into(),
)?;
let asset = AssetId::decode(&mut &asset_bytes[..])
.map_err(|_e| "RecoverEscrowArgumentsFailedOnDecodingArguments")?;
let target_account_bytes = fsx.input.encoded_args.get(1).ok_or::<DispatchError>(
"RecoverEscrowArgumentsFailedToAccessTargetAccountFromField1".into(),
)?;
let target_account = T::AccountId::decode(&mut &target_account_bytes[..])
.map_err(|_e| "RecoverEscrowArgumentsFailedOnDecodingArguments")?;
let escrow_amount_bytes = fsx.input.encoded_args.get(2).ok_or::<DispatchError>(
"RecoverEscrowArgumentsFailedToAccessAmountFromField2".into(),
)?;
let escrow_amount = BalanceOf::<T>::decode(&mut &escrow_amount_bytes[..])
.map_err(|_e| "RecoverEscrowArgumentsFailedOnDecodingArguments")?;
Ok((Some(asset), target_account, escrow_amount))
},
_ => return Err("EscrowExecutionNotApplicableForThisSFXAction".into()),
}
}
fn perform_ddd_hot_swap(
local_ctx: &mut LocalXtxCtx<T, BalanceOf<T>>,
executor: &T::AccountId,
) -> Result<Vec<(XExecSignalId<T>, SideEffectId<T>, T::AccountId, Sfx4bId)>, DispatchError>
{
let mut result: Vec<(XExecSignalId<T>, SideEffectId<T>, T::AccountId, Sfx4bId)> = vec![];
for fsx in local_ctx
.full_side_effects
.iter_mut()
.flat_map(|fsx_vec| fsx_vec.iter_mut())
{
if &fsx.input.action == b"tddd" {
let ddd_asset = fsx
.input
.encoded_args
.get(0)
.ok_or::<DispatchError>("CannotAccessDDDAssetAsField0".into())?;
let ddd_target_amount = fsx
.input
.encoded_args
.get(1)
.ok_or::<DispatchError>("CannotAccessDDDAmountAsField1".into())?;
if ddd_asset == &0u32.encode() {
fsx.input.action = *b"tran";
fsx.input.encoded_args = vec![executor.encode(), ddd_target_amount.clone()];
} else {
fsx.input.action = *b"tass";
fsx.input.encoded_args = vec![
ddd_asset.clone(),
executor.encode(),
ddd_target_amount.clone(),
];
}
let override_sfx_id = fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id);
let override_ddd_fsx = fsx.clone();
FullSideEffects::<T>::mutate(local_ctx.xtx_id, |full_side_effects| {
if let Some(fsx_vec_of_vec) = full_side_effects {
for fsx_vec in fsx_vec_of_vec.iter_mut() {
for (_index, fsx) in fsx_vec.iter_mut().enumerate() {
if fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id)
== override_sfx_id
{
*fsx = override_ddd_fsx.clone();
result.push((
local_ctx.xtx_id,
override_sfx_id,
executor.clone(),
fsx.input.action.clone(),
));
}
}
}
}
});
}
}
Ok(result)
}
}