From 799b68c6d1d11ba69f782e50af26ca352a9842d5 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Thu, 15 Feb 2024 13:46:27 +0530 Subject: [PATCH] update transfers to initiate and then confirm once XDM is confirmed. --- crates/pallet-domains/src/block_tree.rs | 66 ++++-- crates/pallet-domains/src/domain_registry.rs | 2 +- crates/pallet-domains/src/tests.rs | 33 ++- crates/sp-domains/src/lib.rs | 49 +++- domains/client/domain-operator/src/tests.rs | 10 +- domains/pallets/transporter/src/lib.rs | 233 +++++++++++++++---- 6 files changed, 316 insertions(+), 77 deletions(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 862d3dc874..1d918de68c 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -11,9 +11,10 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{ - ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, ExecutionReceipt, OperatorId, + ChainId, ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, ExecutionReceipt, OperatorId, + Transfers, }; -use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedSub, One, Saturating, Zero}; +use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Saturating, Zero}; use sp_std::cmp::Ordering; use sp_std::collections::btree_map::BTreeMap; use sp_std::vec::Vec; @@ -348,26 +349,14 @@ pub(crate) fn process_execution_receipt( execution_receipt.consensus_block_number, ); - // tracking the transfer - // 1. Block fees are burned on domain, so it is considered transferred out - // 2. XDM transfers from the Domain - // 3. XDM transfers into the domain - let transfer_out_balance = execution_receipt + let block_fees = execution_receipt .block_fees .total_fees() - .and_then(|total| total.checked_add(&execution_receipt.transfers.transfers_out)) .ok_or(Error::BalanceOverflow)?; - // track the transfers out and then track transfers in - T::DomainsTransfersTracker::transfer_out(domain_id, transfer_out_balance) + update_domain_transfers::(domain_id, &execution_receipt.transfers, block_fees) .map_err(|_| Error::DomainTransfersTracking)?; - T::DomainsTransfersTracker::transfer_in( - domain_id, - execution_receipt.transfers.transfers_in, - ) - .map_err(|_| Error::DomainTransfersTracking)?; - LatestConfirmedDomainBlock::::insert( domain_id, ConfirmedDomainBlock { @@ -404,6 +393,51 @@ pub(crate) fn process_execution_receipt( Ok(None) } +type TransferTrackerError = + <::DomainsTransfersTracker as DomainsTransfersTracker>>::Error; + +/// Updates domain transfers for following scenarios +/// 1. Block fees are burned on domain +/// 2. Confirming incoming XDM transfers to the Domain +/// 3. Noting outgoing transfers from the domain +/// 4. Cancelling outgoing transfers from the domain. +fn update_domain_transfers( + domain_id: DomainId, + transfers: &Transfers>, + block_fees: BalanceOf, +) -> Result<(), TransferTrackerError> { + let Transfers { + transfers_in, + transfers_out, + transfers_reverted, + } = transfers; + + // confirm incoming transfers + let er_chain_id = ChainId::Domain(domain_id); + transfers_in + .iter() + .try_for_each(|(from_chain_id, amount)| { + T::DomainsTransfersTracker::confirm_transfer(*from_chain_id, er_chain_id, *amount) + })?; + + // note outgoing transfers + transfers_out.iter().try_for_each(|(to_chain_id, amount)| { + T::DomainsTransfersTracker::note_transfer(er_chain_id, *to_chain_id, *amount) + })?; + + // cancel existing transfers + transfers_reverted + .iter() + .try_for_each(|(to_chain_id, amount)| { + T::DomainsTransfersTracker::cancel_transfer(er_chain_id, *to_chain_id, *amount) + })?; + + // deduct execution fees from domain + T::DomainsTransfersTracker::reduce_domain_balance(domain_id, block_fees)?; + + Ok(()) +} + fn add_new_receipt_to_block_tree( domain_id: DomainId, submitter: OperatorId, diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index f269efea9b..4b7b621808 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -175,7 +175,7 @@ pub(crate) fn do_instantiate_domain( ) .map_err(|_| Error::InsufficientFund)?; - T::DomainsTransfersTracker::transfer_in(domain_id, total_issuance) + T::DomainsTransfersTracker::initialize_domain_balance(domain_id, total_issuance) .map_err(|_| Error::TransfersTracker)?; let genesis_receipt = { diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index f4a2a9b217..32d3aef790 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -26,7 +26,7 @@ use sp_domains::merkle_tree::MerkleTree; use sp_domains::proof_provider_and_verifier::StorageProofProvider; use sp_domains::storage::RawGenesis; use sp_domains::{ - BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, ExtrinsicDigest, + BundleHeader, ChainId, DomainId, DomainsHoldIdentifier, ExecutionReceipt, ExtrinsicDigest, InboxedBundle, InvalidBundleType, OpaqueBundle, OperatorAllowList, OperatorId, OperatorPair, ProofOfElection, RuntimeType, SealedBundleHeader, StakingHoldIdentifier, }; @@ -230,15 +230,38 @@ pub struct MockDomainsTransfersTracker; impl sp_domains::DomainsTransfersTracker for MockDomainsTransfersTracker { type Error = (); - fn balance_on_domain(_domain_id: DomainId) -> Result { - Ok(Default::default()) + fn initialize_domain_balance( + _domain_id: DomainId, + _amount: Balance, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn note_transfer( + _from_chain_id: ChainId, + _to_chain_id: ChainId, + _amount: Balance, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn confirm_transfer( + _from_chain_id: ChainId, + _to_chain_id: ChainId, + _amount: Balance, + ) -> Result<(), Self::Error> { + Ok(()) } - fn transfer_in(_domain_id: DomainId, _amount: Balance) -> Result<(), Self::Error> { + fn cancel_transfer( + _from_chain_id: ChainId, + _to_chain_id: ChainId, + _amount: Balance, + ) -> Result<(), Self::Error> { Ok(()) } - fn transfer_out(_domain_id: DomainId, _amount: Balance) -> Result<(), Self::Error> { + fn reduce_domain_balance(_domain_id: DomainId, _amount: Balance) -> Result<(), Self::Error> { Ok(()) } } diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index c7289d4b5e..619e25b62a 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -233,6 +233,14 @@ impl ChainId { ChainId::Domain(_) => false, } } + + #[inline] + pub fn maybe_domain_chain(&self) -> Option { + match self { + ChainId::Consensus => None, + ChainId::Domain(domain_id) => Some(*domain_id), + } + } } impl From for ChainId { @@ -280,9 +288,11 @@ where #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Default)] pub struct Transfers { /// Total transfers that came into the domain. - pub transfers_in: Balance, + pub transfers_in: BTreeMap, /// Total transfers that went out of the domain. - pub transfers_out: Balance, + pub transfers_out: BTreeMap, + /// Total transfers from this domain that were reverted. + pub transfers_reverted: BTreeMap, } #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] @@ -1100,14 +1110,33 @@ impl ExtrinsicDigest { pub trait DomainsTransfersTracker { type Error; - /// Marks transfer into domain. - fn transfer_in(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>; - - /// Marks a transfer from domain. - fn transfer_out(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>; - - /// Returns the total balance on domain. - fn balance_on_domain(domain_id: DomainId) -> Result; + /// Initializes the domain balance + fn initialize_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>; + + /// Notes a transfer between chains. + /// Balance on from_chain_id is reduced if it is a domain chain + fn note_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: Balance, + ) -> Result<(), Self::Error>; + + /// Confirms a transfer between chains. + fn confirm_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: Balance, + ) -> Result<(), Self::Error>; + + /// Cancels an initiated transfer between chains. + fn cancel_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: Balance, + ) -> Result<(), Self::Error>; + + /// Reduces a given amount from the domain balance + fn reduce_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>; } pub type ExecutionReceiptFor = ExecutionReceipt< diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index d37b1e8858..11ce551e81 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -27,8 +27,8 @@ use sp_domain_digests::AsPredigest; use sp_domains::core_api::DomainCoreApi; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{ - Bundle, BundleValidity, DomainsApi, HeaderHashingFor, InboxedBundle, InvalidBundleType, - Transfers, + Bundle, BundleValidity, ChainId, DomainsApi, HeaderHashingFor, InboxedBundle, + InvalidBundleType, Transfers, }; use sp_domains_fraud_proof::fraud_proof::{ ApplyExtrinsicMismatch, ExecutionPhase, FinalizeBlockMismatch, FraudProof, @@ -41,6 +41,7 @@ use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as use sp_runtime::transaction_validity::InvalidTransaction; use sp_runtime::OpaqueExtrinsic; use sp_state_machine::backend::AsTrieBackend; +use std::collections::BTreeMap; use std::sync::Arc; use subspace_core_primitives::PotOutput; use subspace_runtime_primitives::opaque::Block as CBlock; @@ -2017,8 +2018,9 @@ async fn test_invalid_transfers_fraud_proof() { let mut opaque_bundle = bundle.unwrap(); let receipt = &mut opaque_bundle.sealed_header.header.receipt; receipt.transfers = Transfers { - transfers_in: 10 * SSC, - transfers_out: 20 * SSC, + transfers_in: BTreeMap::from([(ChainId::Consensus, 10 * SSC)]), + transfers_out: BTreeMap::from([(ChainId::Consensus, 10 * SSC)]), + transfers_reverted: Default::default(), }; opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice .pair() diff --git a/domains/pallets/transporter/src/lib.rs b/domains/pallets/transporter/src/lib.rs index 9794019e30..305937a51d 100644 --- a/domains/pallets/transporter/src/lib.rs +++ b/domains/pallets/transporter/src/lib.rs @@ -21,11 +21,12 @@ use codec::{Decode, Encode}; use domain_runtime_primitives::{MultiAccountId, TryConvertBack}; +use frame_support::dispatch::DispatchResult; use frame_support::ensure; use frame_support::traits::Currency; pub use pallet::*; use scale_info::TypeInfo; -use sp_domains::DomainId; +use sp_domains::{DomainId, Transfers}; use sp_messenger::messages::ChainId; use sp_runtime::traits::{CheckedAdd, CheckedSub, Get}; @@ -76,13 +77,13 @@ mod pallet { use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons}; use frame_support::weights::Weight; use frame_system::pallet_prelude::*; - use sp_domains::{DomainId, Transfers}; + use sp_domains::{DomainId, DomainsTransfersTracker, Transfers}; use sp_messenger::endpoint::{ Endpoint, EndpointHandler as EndpointHandlerT, EndpointId, EndpointRequest, EndpointResponse, Sender, }; use sp_messenger::messages::ChainId; - use sp_runtime::traits::{CheckedAdd, Convert}; + use sp_runtime::traits::Convert; use sp_std::vec; use sp_std::vec::Vec; @@ -141,6 +142,12 @@ mod pallet { pub(super) type ChainTransfers = StorageValue<_, Transfers>, ValueQuery>; + /// Storage to track unconfirmed transfers between different chains. + #[pallet::storage] + #[pallet::getter(fn unconfirmed_transfers)] + pub(super) type UnconfirmedTransfers = + StorageDoubleMap<_, Identity, ChainId, Identity, ChainId, BalanceOf, ValueQuery>; + /// Events emitted by pallet-transporter. #[pallet::event] #[pallet::generate_deposit(pub (super) fn deposit_event)] @@ -201,6 +208,10 @@ mod pallet { NonConsensusChain, /// Emits when balance overflow BalanceOverflow, + /// Emits when balance underflow + BalanceUnderflow, + /// Emits when domain balance is already initialized + DomainBalanceAlreadyInitialized, } #[pallet::call] @@ -254,13 +265,15 @@ mod pallet { message_id, }); - ChainTransfers::::try_mutate(|transfers| { - transfers.transfers_out = transfers - .transfers_out - .checked_add(&amount) - .ok_or(Error::::BalanceOverflow)?; - Ok::<(), Error>(()) - })?; + // if this is consensus chain, then note the transfer + // else add transfer to storage to send through ER to consensus chain + if T::SelfChainId::get().is_consensus_chain() { + Self::note_transfer(T::SelfChainId::get(), dst_chain_id, amount)? + } else { + ChainTransfers::::try_mutate(|transfers| { + Self::update_transfer_out(transfers, dst_chain_id, amount) + })?; + } Ok(()) } @@ -316,13 +329,15 @@ mod pallet { let _imbalance = T::Currency::deposit_creating(&account_id, req.amount); - ChainTransfers::::try_mutate(|transfers| { - transfers.transfers_in = transfers - .transfers_in - .checked_add(&req.amount) - .ok_or(Error::::BalanceOverflow)?; - Ok::<(), Error>(()) - })?; + // if this is consensus chain, then confirm the transfer + // else add transfer to storage to send through ER to consensus chain + if T::SelfChainId::get().is_consensus_chain() { + Pallet::::confirm_transfer(src_chain_id, T::SelfChainId::get(), req.amount)? + } else { + ChainTransfers::::try_mutate(|transfers| { + Pallet::::update_transfer_in(transfers, src_chain_id, req.amount) + })?; + } frame_system::Pallet::::deposit_event(Into::<::RuntimeEvent>::into( Event::::IncomingTransferSuccessful { @@ -373,13 +388,23 @@ mod pallet { .ok_or(Error::::InvalidAccountId)?; let _imbalance = T::Currency::deposit_creating(&account_id, transfer.amount); - ChainTransfers::::try_mutate(|transfers| { - transfers.transfers_in = transfers - .transfers_in - .checked_add(&transfer.amount) - .ok_or(Error::::BalanceOverflow)?; - Ok::<(), Error>(()) - })?; + // if this is consensus chain, then revert the transfer + // else update the Transfers storage with reverted transfer + if T::SelfChainId::get().is_consensus_chain() { + Pallet::::cancel_transfer( + T::SelfChainId::get(), + dst_chain_id, + transfer.amount, + )?; + } else { + ChainTransfers::::try_mutate(|transfers| { + Pallet::::update_transfer_revert( + transfers, + dst_chain_id, + transfer.amount, + ) + })?; + } frame_system::Pallet::::deposit_event( Into::<::RuntimeEvent>::into( @@ -405,35 +430,97 @@ mod pallet { impl sp_domains::DomainsTransfersTracker> for Pallet { type Error = Error; - fn balance_on_domain(domain_id: DomainId) -> Result, Self::Error> { + fn initialize_domain_balance( + domain_id: DomainId, + amount: BalanceOf, + ) -> Result<(), Self::Error> { + Self::ensure_consensus_chain()?; + ensure!( - T::SelfChainId::get().is_consensus_chain(), - Error::NonConsensusChain + !DomainBalances::::contains_key(domain_id), + Error::DomainBalanceAlreadyInitialized ); - Ok(DomainBalances::::get(domain_id)) + DomainBalances::::set(domain_id, amount); + Ok(()) } - fn transfer_in(domain_id: DomainId, amount: BalanceOf) -> Result<(), Self::Error> { - ensure!( - T::SelfChainId::get().is_consensus_chain(), - Error::NonConsensusChain - ); + fn note_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: BalanceOf, + ) -> Result<(), Self::Error> { + Self::ensure_consensus_chain()?; + + if let Some(domain_id) = from_chain_id.maybe_domain_chain() { + DomainBalances::::try_mutate(domain_id, |current_balance| { + *current_balance = current_balance + .checked_sub(&amount) + .ok_or(Error::LowBalanceOnDomain)?; + Ok(()) + })?; + } - DomainBalances::::try_mutate(domain_id, |current_balance| { - *current_balance = current_balance + UnconfirmedTransfers::::try_mutate(from_chain_id, to_chain_id, |total_amount| { + *total_amount = total_amount .checked_add(&amount) .ok_or(Error::BalanceOverflow)?; Ok(()) - }) + })?; + + Ok(()) } - fn transfer_out(domain_id: DomainId, amount: BalanceOf) -> Result<(), Self::Error> { - ensure!( - T::SelfChainId::get().is_consensus_chain(), - Error::NonConsensusChain - ); + fn confirm_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: BalanceOf, + ) -> Result<(), Self::Error> { + Self::ensure_consensus_chain()?; + UnconfirmedTransfers::::try_mutate(from_chain_id, to_chain_id, |total_amount| { + *total_amount = total_amount + .checked_sub(&amount) + .ok_or(Error::BalanceUnderflow)?; + Ok(()) + })?; + + if let Some(domain_id) = to_chain_id.maybe_domain_chain() { + DomainBalances::::try_mutate(domain_id, |current_balance| { + *current_balance = current_balance + .checked_add(&amount) + .ok_or(Error::BalanceOverflow)?; + Ok(()) + })?; + } + Ok(()) + } + + fn cancel_transfer( + from_chain_id: ChainId, + to_chain_id: ChainId, + amount: BalanceOf, + ) -> Result<(), Self::Error> { + Self::ensure_consensus_chain()?; + UnconfirmedTransfers::::try_mutate(from_chain_id, to_chain_id, |total_amount| { + *total_amount = total_amount + .checked_sub(&amount) + .ok_or(Error::BalanceUnderflow)?; + Ok(()) + })?; + + if let Some(domain_id) = from_chain_id.maybe_domain_chain() { + DomainBalances::::try_mutate(domain_id, |current_balance| { + *current_balance = current_balance + .checked_add(&amount) + .ok_or(Error::BalanceOverflow)?; + Ok(()) + })?; + } + Ok(()) + } + + fn reduce_domain_balance(domain_id: DomainId, amount: BalanceOf) -> Result<(), Self::Error> { DomainBalances::::try_mutate(domain_id, |current_balance| { *current_balance = current_balance .checked_sub(&amount) @@ -442,3 +529,67 @@ impl sp_domains::DomainsTransfersTracker> for Pallet }) } } + +impl Pallet { + fn ensure_consensus_chain() -> Result<(), Error> { + ensure!( + T::SelfChainId::get().is_consensus_chain(), + Error::NonConsensusChain + ); + + Ok(()) + } + + fn update_transfer_out( + transfers: &mut Transfers>, + to_chain_id: ChainId, + amount: BalanceOf, + ) -> DispatchResult { + let total_transfer = + if let Some(current_transfer_amount) = transfers.transfers_out.get(&to_chain_id) { + current_transfer_amount + .checked_add(&amount) + .ok_or(Error::::BalanceOverflow)? + } else { + amount + }; + transfers.transfers_out.insert(to_chain_id, total_transfer); + Ok(()) + } + + fn update_transfer_in( + transfers: &mut Transfers>, + from_chain_id: ChainId, + amount: BalanceOf, + ) -> DispatchResult { + let total_transfer = + if let Some(current_transfer_amount) = transfers.transfers_in.get(&from_chain_id) { + current_transfer_amount + .checked_add(&amount) + .ok_or(Error::::BalanceOverflow)? + } else { + amount + }; + transfers.transfers_in.insert(from_chain_id, total_transfer); + Ok(()) + } + + fn update_transfer_revert( + transfers: &mut Transfers>, + to_chain_id: ChainId, + amount: BalanceOf, + ) -> DispatchResult { + let total_transfer = + if let Some(current_transfer_amount) = transfers.transfers_reverted.get(&to_chain_id) { + current_transfer_amount + .checked_add(&amount) + .ok_or(Error::::BalanceOverflow)? + } else { + amount + }; + transfers + .transfers_reverted + .insert(to_chain_id, total_transfer); + Ok(()) + } +}