Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DNM] Coretime revenue: asset teleport draft #4811

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,89 @@ use cumulus_pallet_parachain_system::RelaychainDataProvider;
use cumulus_primitives_core::relay_chain;
use frame_support::{
parameter_types,
storage::with_transaction,
traits::{
fungible::{Balanced, Credit},
OnUnbalanced,
fungible::{Balanced, Credit, Inspect},
tokens::{Fortitude, Preservation},
DefensiveResult, OnUnbalanced,
},
};
use pallet_broker::{
CoreAssignment, CoreIndex, CoretimeInterface, OnDemandRevenueRecord, PartsOf57600,
RCBlockNumberOf, RevenueInbox,
traits::NewTimesliceHook, CoreAssignment, CoreIndex, CoretimeInterface, OnDemandRevenueRecord,
PartsOf57600, RCBlockNumberOf, RevenueInbox,
};
use parachains_common::{AccountId, Balance};
use rococo_runtime_constants::system_parachain::coretime;
use sp_runtime::{traits::AccountIdConversion, TransactionOutcome};
use xcm::latest::prelude::*;
use xcm_executor::traits::TransactAsset;

pub struct CreditToCollatorPot;
impl OnUnbalanced<Credit<AccountId, Balances>> for CreditToCollatorPot {
fn on_nonzero_unbalanced(credit: Credit<AccountId, Balances>) {
let staking_pot = CollatorSelection::account_id();
let _ = <Balances as Balanced<_>>::resolve(&staking_pot, credit);
pub struct StashToBurn;
impl OnUnbalanced<Credit<AccountId, Balances>> for StashToBurn {
fn on_nonzero_unbalanced(amount: Credit<AccountId, Balances>) {
s0me0ne-unkn0wn marked this conversation as resolved.
Show resolved Hide resolved
Balances::resolve(&BurnStashAccount::get(), amount).defensive_ok();
}
}

type AssetTransactor = <xcm_config::XcmConfig as xcm_executor::Config>::AssetTransactor;

fn burn_at_relay(stash: &AccountId, value: Balance) -> Result<(), XcmError> {
let dest = Location::parent();
let stash_location =
Junction::AccountId32 { network: None, id: stash.clone().into() }.into_location();
let asset = Asset { id: AssetId(Location::parent()), fun: Fungible(value) };
let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };

let withdrawn = AssetTransactor::withdraw_asset(&asset, &stash_location, None)?;

AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;

let parent_assets = Into::<Assets>::into(withdrawn)
.reanchored(&dest, &Here.into())
.defensive_map_err(|_| XcmError::ReanchorFailed)?;

PolkadotXcm::send_xcm(
Here,
Location::parent(),
Xcm(vec![
Instruction::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: None,
},
ReceiveTeleportedAsset(parent_assets.clone()),
BurnAsset(parent_assets),
]),
)?;

AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);

Ok(())
}

pub struct BurnStash;
impl NewTimesliceHook for BurnStash {
fn on_new_timeslice(_t: pallet_broker::Timeslice) {
let stash = BurnStashAccount::get();
let value = Balances::reducible_balance(&stash, Preservation::Expendable, Fortitude::Force);

if value > 0 {
log::debug!(target: "runtime::coretime", "Going to burn {value} stashed tokens at RC");
with_transaction(|| -> TransactionOutcome<Result<(), DispatchError>> {
match burn_at_relay(&stash, value) {
Ok(()) => {
log::debug!(target: "runtime::coretime", "Succesfully burnt {value} tokens");
TransactionOutcome::Commit(Ok(()))
},
Err(err) => {
log::error!(target: "runtime::coretime", "burn_at_relay failed: {err:?}");
TransactionOutcome::Rollback(Err(DispatchError::Other(
"Failed to burn funds on relay chain",
)))
},
}
})
.defensive_ok();
}
}
}

Expand Down Expand Up @@ -70,6 +135,7 @@ enum CoretimeProviderCalls {

parameter_types! {
pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
pub BurnStashAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
}

/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate
Expand Down Expand Up @@ -220,7 +286,8 @@ impl CoretimeInterface for CoretimeAllocator {
impl pallet_broker::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type OnRevenue = CreditToCollatorPot;
type OnRevenue = StashToBurn;
type OnNewTimeslice = BurnStash;
type TimeslicePeriod = ConstU32<{ coretime::TIMESLICE_PERIOD }>;
type MaxLeasedCores = ConstU32<50>;
type MaxReservedCores = ConstU32<10>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,9 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}

// TODO: Re-run benchmarks
fn on_new_timeslice() -> Weight {
Weight::zero()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,89 @@ use cumulus_pallet_parachain_system::RelaychainDataProvider;
use cumulus_primitives_core::relay_chain;
use frame_support::{
parameter_types,
storage::with_transaction,
traits::{
fungible::{Balanced, Credit},
OnUnbalanced,
fungible::{Balanced, Credit, Inspect},
tokens::{Fortitude, Preservation},
DefensiveResult, OnUnbalanced,
},
};
use pallet_broker::{
CoreAssignment, CoreIndex, CoretimeInterface, OnDemandRevenueRecord, PartsOf57600,
RCBlockNumberOf, RevenueInbox,
traits::NewTimesliceHook, CoreAssignment, CoreIndex, CoretimeInterface, OnDemandRevenueRecord,
PartsOf57600, RCBlockNumberOf, RevenueInbox,
};
use parachains_common::{AccountId, Balance};
use sp_runtime::{traits::AccountIdConversion, TransactionOutcome};
use westend_runtime_constants::system_parachain::coretime;
use xcm::latest::prelude::*;
use xcm_executor::traits::TransactAsset;

pub struct CreditToCollatorPot;
impl OnUnbalanced<Credit<AccountId, Balances>> for CreditToCollatorPot {
fn on_nonzero_unbalanced(credit: Credit<AccountId, Balances>) {
let staking_pot = CollatorSelection::account_id();
let _ = <Balances as Balanced<_>>::resolve(&staking_pot, credit);
pub struct StashToBurn;
impl OnUnbalanced<Credit<AccountId, Balances>> for StashToBurn {
fn on_nonzero_unbalanced(amount: Credit<AccountId, Balances>) {
Balances::resolve(&BurnStashAccount::get(), amount).defensive_ok();
}
}

type AssetTransactor = <xcm_config::XcmConfig as xcm_executor::Config>::AssetTransactor;

fn burn_at_relay(stash: &AccountId, value: Balance) -> Result<(), XcmError> {
let dest = Location::parent();
let stash_location =
Junction::AccountId32 { network: None, id: stash.clone().into() }.into_location();
let asset = Asset { id: AssetId(Location::parent()), fun: Fungible(value) };
let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };

let withdrawn = AssetTransactor::withdraw_asset(&asset, &stash_location, None)?;

AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;

let parent_assets = Into::<Assets>::into(withdrawn)
.reanchored(&dest, &Here.into())
.defensive_map_err(|_| XcmError::ReanchorFailed)?;

PolkadotXcm::send_xcm(
Here,
Location::parent(),
Xcm(vec![
Instruction::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: None,
},
ReceiveTeleportedAsset(parent_assets.clone()),
BurnAsset(parent_assets),
]),
)?;

AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);

Ok(())
}

pub struct BurnStash;
impl NewTimesliceHook for BurnStash {
fn on_new_timeslice(_t: pallet_broker::Timeslice) {
let stash = BurnStashAccount::get();
let value = Balances::reducible_balance(&stash, Preservation::Expendable, Fortitude::Force);

if value > 0 {
log::debug!(target: "runtime::coretime", "Going to burn {value} stashed tokens at RC");
with_transaction(|| -> TransactionOutcome<Result<(), DispatchError>> {
match burn_at_relay(&stash, value) {
Ok(()) => {
log::debug!(target: "runtime::coretime", "Succesfully burnt {value} tokens");
TransactionOutcome::Commit(Ok(()))
},
Err(err) => {
log::error!(target: "runtime::coretime", "burn_at_relay failed: {err:?}");
TransactionOutcome::Rollback(Err(DispatchError::Other(
"Failed to burn funds on relay chain",
)))
},
}
})
.defensive_ok();
}
}
}

Expand Down Expand Up @@ -70,6 +135,7 @@ enum CoretimeProviderCalls {

parameter_types! {
pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
pub BurnStashAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
}

/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate
Expand Down Expand Up @@ -232,7 +298,8 @@ impl CoretimeInterface for CoretimeAllocator {
impl pallet_broker::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type OnRevenue = CreditToCollatorPot;
type OnRevenue = StashToBurn;
type OnNewTimeslice = BurnStash;
type TimeslicePeriod = ConstU32<{ coretime::TIMESLICE_PERIOD }>;
// We don't actually need any leases at launch but set to 10 in case we want to sudo some in.
type MaxLeasedCores = ConstU32<10>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,6 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
// TODO: Re-run bencmarks!
fn on_new_timeslice() -> frame_support::weights::Weight { Weight::zero() }
}
6 changes: 5 additions & 1 deletion cumulus/polkadot-parachain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,8 @@ try-runtime = [
"shell-runtime/try-runtime",
"sp-runtime/try-runtime",
]
fast-runtime = ["bridge-hub-rococo-runtime/fast-runtime"]
fast-runtime = [
"bridge-hub-rococo-runtime/fast-runtime",
"coretime-rococo-runtime/fast-runtime",
"coretime-westend-runtime/fast-runtime",
]
80 changes: 57 additions & 23 deletions polkadot/runtime/parachains/src/assigner_on_demand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use frame_support::{
use frame_system::pallet_prelude::*;
use polkadot_primitives::{CoreIndex, Id as ParaId};
use sp_runtime::{
traits::{AccountIdConversion, One, SaturatedConversion, Zero},
traits::{AccountIdConversion, One, SaturatedConversion},
FixedPointNumber, FixedPointOperand, FixedU128, Perbill, Saturating,
};
use sp_std::prelude::*;
Expand Down Expand Up @@ -88,6 +88,24 @@ impl WeightInfo for TestWeightInfo {
}
}

/// A revenue claim that has been started but is not commited yet.
/// Produced by `start_revenue_claim_until` and consumed then by `commit_revenue_claim`.
pub struct RevenueClaim<T: Config> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some docs would be good. What is the purpose of this struct? What is a split_off? ..

/// The index in the `Revenue` storage where the revenue history of this claim begins. Committing
/// the claim will truncate the `Revenue` storage by this point. If `None`, no claim is possible
/// (the cmail is in the future or is too old)
split_off: Option<usize>,
/// The amount that will be claimed when the claim is commited. It's a sum of all the revenue
/// history entries beyond `split_off`.
pub amount: BalanceOf<T>,
}

impl<T: Config> Default for RevenueClaim<T> {
fn default() -> Self {
Self { split_off: None, amount: 0u32.into() }
}
}

#[frame_support::pallet]
pub mod pallet {

Expand Down Expand Up @@ -396,8 +414,8 @@ where
Error::<T>::QueueFull
);

// Charge the sending account the spot price. The amount will be burnt once the
// broker chain requests revenue information.
// Charge the sending account the spot price. The amount will be teleported to the
// broker chain once it requests revenue information.
let amt = T::Currency::withdraw(
&sender,
spot_price,
Expand Down Expand Up @@ -636,29 +654,45 @@ where
})
}

/// Collect the revenue from the `when` blockheight
pub fn claim_revenue_until(when: BlockNumberFor<T>) -> BalanceOf<T> {
/// Start claiming the revenue from the `when` blockheight. The claim should be later commited
/// with `commit_revenue_claim`
pub fn start_revenue_claim_until(when: BlockNumberFor<T>) -> RevenueClaim<T> {
let now = <frame_system::Pallet<T>>::block_number();
let mut amount: BalanceOf<T> = BalanceOf::<T>::zero();
Revenue::<T>::mutate(|revenue| {
while !revenue.is_empty() {
let index = (revenue.len() - 1) as u32;
if when > now.saturating_sub(index.into()) {
amount = amount.saturating_add(revenue.pop().defensive_unwrap_or(0u32.into()));
} else {
break
}
}
});

let imbalance = T::Currency::burn(amount);
let _ = T::Currency::settle(
&Self::account_id(),
imbalance,
WithdrawReasons::FEE,
ExistenceRequirement::AllowDeath,
if when > now {
log::warn!(
target: LOG_TARGET,
"Tried to claim future revenue until block {when:?} at block {now:?}"
);
return Default::default();
}

let split_off: usize = 1 + (now - when).try_into().unwrap_or(usize::MAX - 1);
let revenue = Revenue::<T>::get();

log::debug!(
target: LOG_TARGET,
"Started claiming revenue until block {when:?} at block {now:?}, split {} revenue entries at {split_off}",
revenue.len(),
);
amount

if split_off < revenue.len() {
RevenueClaim {
split_off: Some(split_off),
amount: revenue[split_off..].iter().fold(0u32.into(), |acc, x| acc + *x),
}
} else {
Default::default()
}
}

/// Commit previously created revenue claim
pub fn commit_revenue_claim(claim: RevenueClaim<T>) {
if let Some(split_off) = claim.split_off {
Revenue::<T>::mutate(|revenue| {
revenue.truncate(split_off);
});
}
}

pub fn account_id() -> T::AccountId {
Expand Down
Loading
Loading