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

On-demand credits #5990

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 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
10 changes: 5 additions & 5 deletions polkadot/runtime/parachains/src/assigner_coretime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ use crate::{
assigner_coretime::{mock_helpers::GenesisConfigBuilder, pallet::Error, Schedule},
initializer::SessionChangeNotification,
mock::{
new_test_ext, Balances, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin,
Scheduler, System, Test,
new_test_ext, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin, Scheduler,
System, Test,
},
paras::{ParaGenesisArgs, ParaKind},
scheduler::common::Assignment,
};
use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency};
use frame_support::{assert_noop, assert_ok, pallet_prelude::*};
use pallet_broker::TaskId;
use polkadot_primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};

Expand Down Expand Up @@ -494,9 +494,9 @@ fn pop_assignment_for_core_works() {
// Initialize the parathread, wait for it to be ready, then add an
// on demand order to later pop with our Coretime assigner.
schedule_blank_para(para_id, ParaKind::Parathread);
Balances::make_free_balance_be(&alice, amt);
on_demand::Credits::<Test>::insert(&alice, amt);
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
assert_ok!(OnDemand::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id));
assert_ok!(OnDemand::place_order_with_credits(RuntimeOrigin::signed(alice), amt, para_id));

// Case 1: Assignment idle
assert_ok!(CoretimeAssigner::assign_core(
Expand Down
10 changes: 10 additions & 0 deletions polkadot/runtime/parachains/src/coretime/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,14 @@ mod benchmarks {
Some(BlockNumberFor::<T>::from(20u32)),
)
}

#[benchmark]
fn credit_account() {
// Setup
let root_origin = <T as frame_system::Config>::RuntimeOrigin::root();
let who: T::AccountId = whitelisted_caller();

#[extrinsic_call]
_(root_origin as <T as frame_system::Config>::RuntimeOrigin, who, 1_000_000u32.into())
}
}
42 changes: 20 additions & 22 deletions polkadot/runtime/parachains/src/coretime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const LOG_TARGET: &str = "runtime::parachains::coretime";
pub trait WeightInfo {
fn request_core_count() -> Weight;
fn request_revenue_at() -> Weight;
//fn credit_account() -> Weight;
fn credit_account() -> Weight;
fn assign_core(s: u32) -> Weight;
}

Expand All @@ -62,19 +62,18 @@ impl WeightInfo for TestWeightInfo {
fn request_revenue_at() -> Weight {
Weight::MAX
}
// TODO: Add real benchmarking functionality for each of these to
// benchmarking.rs, then uncomment here and in trait definition.
//fn credit_account() -> Weight {
// Weight::MAX
//}
fn credit_account() -> Weight {
Weight::MAX
}
fn assign_core(_s: u32) -> Weight {
Weight::MAX
}
}

/// Shorthand for the Balance type the runtime is using.
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::Balance;

/// Broker pallet index on the coretime chain. Used to
///
Expand Down Expand Up @@ -120,8 +119,6 @@ pub mod pallet {
type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
+ Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The runtime's definition of a Currency.
type Currency: Currency<Self::AccountId>;
/// The ParaId of the coretime chain.
#[pallet::constant]
type BrokerId: Get<u32>;
Expand Down Expand Up @@ -195,18 +192,19 @@ pub mod pallet {
Self::notify_revenue(when)
}

//// TODO Impl me!
////#[pallet::weight(<T as Config>::WeightInfo::credit_account())]
//#[pallet::call_index(3)]
//pub fn credit_account(
// origin: OriginFor<T>,
// _who: T::AccountId,
// _amount: BalanceOf<T>,
//) -> DispatchResult {
// // Ignore requests not coming from the coretime chain or root.
// Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
// Ok(())
//}
#[pallet::weight(<T as Config>::WeightInfo::credit_account())]
#[pallet::call_index(3)]
pub fn credit_account(
origin: OriginFor<T>,
who: T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
// Ignore requests not coming from the coretime chain or root.
Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;

on_demand::Pallet::<T>::credit_account(who, amount.saturated_into());
Ok(())
}

/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
/// to be used.
Expand Down
1 change: 0 additions & 1 deletion polkadot/runtime/parachains/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,6 @@ impl Get<InteriorLocation> for BrokerPot {
impl coretime::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeEvent = RuntimeEvent;
type Currency = pallet_balances::Pallet<Test>;
type BrokerId = BrokerId;
type WeightInfo = crate::coretime::TestWeightInfo;
type SendXcm = DummyXcmSender;
Expand Down
14 changes: 14 additions & 0 deletions polkadot/runtime/parachains/src/on_demand/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ mod benchmarks {
_(RawOrigin::Signed(caller.into()), BalanceOf::<T>::max_value(), para_id)
}

#[benchmark]
fn place_order_with_credits(s: Linear<1, MAX_FILL_BENCH>) {
// Setup
let caller: T::AccountId = whitelisted_caller();
let para_id = ParaId::from(111u32);
init_parathread::<T>(para_id);
Credits::<T>::insert(&caller, BalanceOf::<T>::max_value());

Pallet::<T>::populate_queue(para_id, s);

#[extrinsic_call]
_(RawOrigin::Signed(caller.into()), BalanceOf::<T>::max_value(), para_id)
}

impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(
Expand Down
131 changes: 113 additions & 18 deletions polkadot/runtime/parachains/src/on_demand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub use pallet::*;
pub trait WeightInfo {
fn place_order_allow_death(s: u32) -> Weight;
fn place_order_keep_alive(s: u32) -> Weight;
fn place_order_with_credits(s: u32) -> Weight;
}

/// A weight info that is only suitable for testing.
Expand All @@ -86,6 +87,19 @@ impl WeightInfo for TestWeightInfo {
fn place_order_keep_alive(_: u32) -> Weight {
Weight::MAX
}

fn place_order_with_credits(_: u32) -> Weight {
Weight::MAX
}
}

/// Defines how the account wants to pay for on-demand.
#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone, Eq)]
enum PaymentType {
/// Use credits to purchase on-demand coretime.
Credits,
/// Use account's free balance to purchase on-demand coretime.
Balance,
}

#[frame_support::pallet]
Expand Down Expand Up @@ -169,6 +183,11 @@ pub mod pallet {
pub type Revenue<T: Config> =
StorageValue<_, BoundedVec<BalanceOf<T>, T::MaxHistoricalRevenue>, ValueQuery>;

/// Keeps track of credits owned by each account.
#[pallet::storage]
pub type Credits<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Expand All @@ -185,6 +204,8 @@ pub mod pallet {
/// The current spot price is higher than the max amount specified in the `place_order`
/// call, making it invalid.
SpotPriceHigherThanMaxAmount,
/// The account doesn't have enough credits to purchase on-demand coretime.
InsufficientCredits,
}

#[pallet::hooks]
Expand Down Expand Up @@ -235,13 +256,21 @@ pub mod pallet {
/// - `OnDemandOrderPlaced`
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::place_order_allow_death(QueueStatus::<T>::get().size()))]
#[allow(deprecated)]
#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
pub fn place_order_allow_death(
origin: OriginFor<T>,
max_amount: BalanceOf<T>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
Pallet::<T>::do_place_order(sender, max_amount, para_id, AllowDeath)
Pallet::<T>::do_place_order(
sender,
max_amount,
para_id,
AllowDeath,
PaymentType::Balance,
)
}

/// Same as the [`place_order_allow_death`](Self::place_order_allow_death) call , but with a
Expand All @@ -261,13 +290,53 @@ pub mod pallet {
/// - `OnDemandOrderPlaced`
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::place_order_keep_alive(QueueStatus::<T>::get().size()))]
#[allow(deprecated)]
#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
pub fn place_order_keep_alive(
origin: OriginFor<T>,
max_amount: BalanceOf<T>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
Pallet::<T>::do_place_order(sender, max_amount, para_id, KeepAlive)
Pallet::<T>::do_place_order(
sender,
max_amount,
para_id,
KeepAlive,
PaymentType::Balance,
)
}

/// Same as the [`place_order_allow_death`](Self::place_order_allow_death) call, but paying
/// for order with credits on-demand credits.
Szegoo marked this conversation as resolved.
Show resolved Hide resolved
///
/// Parameters:
/// - `origin`: The sender of the call, funds will be withdrawn from this account.
Szegoo marked this conversation as resolved.
Show resolved Hide resolved
/// - `max_amount`: The maximum credit to spend from the origin to place an order.
Szegoo marked this conversation as resolved.
Show resolved Hide resolved
/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
///
/// Errors:
/// - `InsufficientCredits`
/// - `QueueFull`
/// - `SpotPriceHigherThanMaxAmount`
///
/// Events:
/// - `OnDemandOrderPlaced`
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::place_order_with_credits(QueueStatus::<T>::get().size()))]
pub fn place_order_with_credits(
origin: OriginFor<T>,
max_amount: BalanceOf<T>,
para_id: ParaId,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
Pallet::<T>::do_place_order(
sender,
max_amount,
para_id,
KeepAlive,
PaymentType::Credits,
)
}
}
}
Expand Down Expand Up @@ -349,6 +418,18 @@ where
});
}

/// Adds credits to the specified account.
///
/// Parameters:
/// - `who`: Credit receiver.
/// - `amount`: The amount of new credits the account will receive.
pub fn credit_account(who: T::AccountId, amount: BalanceOf<T>) {
Credits::<T>::mutate(who, |old_credits| {
let credits = old_credits.saturating_add(amount);
credits
});
}

/// Helper function for `place_order_*` calls. Used to differentiate between placing orders
/// with a keep alive check or to allow the account to be reaped. The amount charged is
/// stored to the pallet account to be later paid out as revenue.
Expand All @@ -358,6 +439,7 @@ where
/// - `max_amount`: The maximum balance to withdraw from the origin to place an order.
/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
/// - `existence_requirement`: Whether or not to ensure that the account will not be reaped.
/// - `payment_type`: Defines how the user wants to pay for on-demand.
///
/// Errors:
/// - `InsufficientBalance`: from the Currency implementation
Expand All @@ -371,6 +453,7 @@ where
max_amount: BalanceOf<T>,
para_id: ParaId,
existence_requirement: ExistenceRequirement,
payment_type: PaymentType,
) -> DispatchResult {
let config = configuration::ActiveConfig::<T>::get();

Expand All @@ -391,22 +474,34 @@ where
Error::<T>::QueueFull
);

// 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,
WithdrawReasons::FEE,
existence_requirement,
)?;

// Consume the negative imbalance and deposit it into the pallet account. Make sure the
// account preserves even without the existential deposit.
let pot = Self::account_id();
if !System::<T>::account_exists(&pot) {
System::<T>::inc_providers(&pot);
match payment_type {
PaymentType::Balance => {
// 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,
WithdrawReasons::FEE,
existence_requirement,
)?;

// Consume the negative imbalance and deposit it into the pallet account. Make
// sure the account preserves even without the existential deposit.
let pot = Self::account_id();
if !System::<T>::account_exists(&pot) {
System::<T>::inc_providers(&pot);
}
T::Currency::resolve_creating(&pot, amt);
},
PaymentType::Credits => {
// Charge the sending account the spot price in credits.
Credits::<T>::try_mutate(&sender, |credits| -> DispatchResult {
ensure!(spot_price <= *credits, Error::<T>::InsufficientCredits);
*credits = credits.saturating_sub(spot_price);
Ok(())
})?;
},
Copy link
Member

Choose a reason for hiding this comment

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

I think we might need adjustments to revenue tracking. Coretime bought with credits don't need a teleport, while coretime bought with balances does. We should also deprecate orders based on balances, to give people a heads up before later removal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, so we want to make place_order_allow_death and place_order_keep_alive deprecated so we can remove them later. This makes sense.

Just to confirm that I interpreted the first part of your comment correctly:

When using credits, the DOT used to purchase them is already on the Coretime chain. However, when using rc balance to make an order, the relay chain must transfer this revenue to the Coretime chain. So, if I understand correctly, you are saying that we need to adjust revenue tracking to account for this teleport when using rc balance?

Copy link
Member

Choose a reason for hiding this comment

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

Precisely!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given that the RC doesn't pay for notifying revenue, is there anything we should account for? UnpaidExecution

Copy link
Member

Choose a reason for hiding this comment

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

I don't think so. What are you thinking about?

}
T::Currency::resolve_creating(&pot, amt);

// Add the amount to the current block's (index 0) revenue information.
Revenue::<T>::mutate(|bounded_revenue| {
Expand Down Expand Up @@ -619,7 +714,7 @@ where

/// Increases the affinity of a `ParaId` to a specified `CoreIndex`.
/// Adds to the count of the `CoreAffinityCount` if an entry is found and the core_index
/// matches. A non-existent entry will be initialized with a count of 1 and uses the supplied
/// matches. A non-existent entry will be initialized with a count of 1 and uses the supplied
/// `CoreIndex`.
fn increase_affinity(para_id: ParaId, core_index: CoreIndex) {
ParaIdAffinity::<T>::mutate(para_id, |maybe_affinity| match maybe_affinity {
Expand Down
Loading
Loading