coretime_rococo_runtime/
coretime.rs

1// Copyright 2022 Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3
4// Cumulus is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Cumulus is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{xcm_config::LocationToAccountId, *};
18use codec::{Decode, Encode};
19use cumulus_pallet_parachain_system::RelaychainDataProvider;
20use cumulus_primitives_core::relay_chain;
21use frame_support::{
22	parameter_types,
23	traits::{
24		fungible::{Balanced, Credit, Inspect},
25		tokens::{Fortitude, Preservation},
26		DefensiveResult, OnUnbalanced,
27	},
28};
29use frame_system::Pallet as System;
30use pallet_broker::{
31	CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, RCBlockNumberOf, TaskId,
32};
33use parachains_common::{AccountId, Balance};
34use rococo_runtime_constants::system_parachain::coretime;
35use sp_runtime::traits::{AccountIdConversion, MaybeConvert};
36use xcm::latest::prelude::*;
37use xcm_executor::traits::{ConvertLocation, TransactAsset};
38
39pub struct BurnCoretimeRevenue;
40impl OnUnbalanced<Credit<AccountId, Balances>> for BurnCoretimeRevenue {
41	fn on_nonzero_unbalanced(amount: Credit<AccountId, Balances>) {
42		let acc = RevenueAccumulationAccount::get();
43		if !System::<Runtime>::account_exists(&acc) {
44			System::<Runtime>::inc_providers(&acc);
45		}
46		Balances::resolve(&acc, amount).defensive_ok();
47	}
48}
49
50type AssetTransactor = <xcm_config::XcmConfig as xcm_executor::Config>::AssetTransactor;
51
52fn burn_at_relay(stash: &AccountId, value: Balance) -> Result<(), XcmError> {
53	let dest = Location::parent();
54	let stash_location =
55		Junction::AccountId32 { network: None, id: stash.clone().into() }.into_location();
56	let asset = Asset { id: AssetId(Location::parent()), fun: Fungible(value) };
57	let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };
58
59	let withdrawn = AssetTransactor::withdraw_asset(&asset, &stash_location, None)?;
60
61	AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;
62
63	let parent_assets = Into::<Assets>::into(withdrawn)
64		.reanchored(&dest, &Here.into())
65		.defensive_map_err(|_| XcmError::ReanchorFailed)?;
66
67	PolkadotXcm::send_xcm(
68		Here,
69		Location::parent(),
70		Xcm(vec![
71			Instruction::UnpaidExecution {
72				weight_limit: WeightLimit::Unlimited,
73				check_origin: None,
74			},
75			ReceiveTeleportedAsset(parent_assets.clone()),
76			BurnAsset(parent_assets),
77		]),
78	)?;
79
80	AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);
81
82	Ok(())
83}
84
85/// A type containing the encoding of the coretime pallet in the Relay chain runtime. Used to
86/// construct any remote calls. The codec index must correspond to the index of `Coretime` in the
87/// `construct_runtime` of the Relay chain.
88#[derive(Encode, Decode)]
89enum RelayRuntimePallets {
90	#[codec(index = 74)]
91	Coretime(CoretimeProviderCalls),
92}
93
94/// Call encoding for the calls needed from the relay coretime pallet.
95#[derive(Encode, Decode)]
96enum CoretimeProviderCalls {
97	#[codec(index = 1)]
98	RequestCoreCount(CoreIndex),
99	#[codec(index = 2)]
100	RequestRevenueInfoAt(relay_chain::BlockNumber),
101	#[codec(index = 3)]
102	CreditAccount(AccountId, Balance),
103	#[codec(index = 4)]
104	AssignCore(
105		CoreIndex,
106		relay_chain::BlockNumber,
107		Vec<(CoreAssignment, PartsOf57600)>,
108		Option<relay_chain::BlockNumber>,
109	),
110}
111
112parameter_types! {
113	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
114	pub RevenueAccumulationAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
115}
116
117/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate
118/// from the parachain context. That is, the parachain provides a market (broker) for the sale of
119/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores.
120pub struct CoretimeAllocator;
121impl CoretimeInterface for CoretimeAllocator {
122	type AccountId = AccountId;
123	type Balance = Balance;
124	type RelayChainBlockNumberProvider = RelaychainDataProvider<Runtime>;
125
126	fn request_core_count(count: CoreIndex) {
127		use crate::coretime::CoretimeProviderCalls::RequestCoreCount;
128		let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count));
129
130		let message = Xcm(vec![
131			Instruction::UnpaidExecution {
132				weight_limit: WeightLimit::Unlimited,
133				check_origin: None,
134			},
135			Instruction::Transact {
136				origin_kind: OriginKind::Native,
137				call: request_core_count_call.encode().into(),
138				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
139			},
140		]);
141
142		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
143			Ok(_) => log::info!(
144				target: "runtime::coretime",
145				"Request to update schedulable cores sent successfully."
146			),
147			Err(e) => log::error!(
148				target: "runtime::coretime",
149				"Failed to send request to update schedulable cores: {:?}",
150				e
151			),
152		}
153	}
154
155	fn request_revenue_info_at(when: RCBlockNumberOf<Self>) {
156		use crate::coretime::CoretimeProviderCalls::RequestRevenueInfoAt;
157		let request_revenue_info_at_call =
158			RelayRuntimePallets::Coretime(RequestRevenueInfoAt(when));
159
160		let message = Xcm(vec![
161			Instruction::UnpaidExecution {
162				weight_limit: WeightLimit::Unlimited,
163				check_origin: None,
164			},
165			Instruction::Transact {
166				origin_kind: OriginKind::Native,
167				call: request_revenue_info_at_call.encode().into(),
168				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
169			},
170		]);
171
172		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
173			Ok(_) => log::info!(
174				target: "runtime::coretime",
175				"Request for revenue information sent successfully."
176			),
177			Err(e) => log::error!(
178				target: "runtime::coretime",
179				"Request for revenue information failed to send: {:?}",
180				e
181			),
182		}
183	}
184
185	fn credit_account(who: Self::AccountId, amount: Self::Balance) {
186		use crate::coretime::CoretimeProviderCalls::CreditAccount;
187		let credit_account_call = RelayRuntimePallets::Coretime(CreditAccount(who, amount));
188
189		let message = Xcm(vec![
190			Instruction::UnpaidExecution {
191				weight_limit: WeightLimit::Unlimited,
192				check_origin: None,
193			},
194			Instruction::Transact {
195				origin_kind: OriginKind::Native,
196				call: credit_account_call.encode().into(),
197				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
198			},
199		]);
200
201		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
202			Ok(_) => log::info!(
203				target: "runtime::coretime",
204				"Instruction to credit account sent successfully."
205			),
206			Err(e) => log::error!(
207				target: "runtime::coretime",
208				"Instruction to credit account failed to send: {:?}",
209				e
210			),
211		}
212	}
213
214	fn assign_core(
215		core: CoreIndex,
216		begin: RCBlockNumberOf<Self>,
217		assignment: Vec<(CoreAssignment, PartsOf57600)>,
218		end_hint: Option<RCBlockNumberOf<Self>>,
219	) {
220		use crate::coretime::CoretimeProviderCalls::AssignCore;
221
222		// The relay chain currently only allows `assign_core` to be called with a complete mask
223		// and only ever with increasing `begin`. The assignments must be truncated to avoid
224		// dropping that core's assignment completely.
225
226		// This shadowing of `assignment` is temporary and can be removed when the relay can accept
227		// multiple messages to assign a single core.
228		let assignment = if assignment.len() > 28 {
229			let mut total_parts = 0u16;
230			// Account for missing parts with a new `Idle` assignment at the start as
231			// `assign_core` on the relay assumes this is sorted. We'll add the rest of the
232			// assignments and sum the parts in one pass, so this is just initialized to 0.
233			let mut assignment_truncated = vec![(CoreAssignment::Idle, 0)];
234			// Truncate to first 27 non-idle assignments.
235			assignment_truncated.extend(
236				assignment
237					.into_iter()
238					.filter(|(a, _)| *a != CoreAssignment::Idle)
239					.take(27)
240					.inspect(|(_, parts)| total_parts += *parts)
241					.collect::<Vec<_>>(),
242			);
243
244			// Set the parts of the `Idle` assignment we injected at the start of the vec above.
245			assignment_truncated[0].1 = 57_600u16.saturating_sub(total_parts);
246			assignment_truncated
247		} else {
248			assignment
249		};
250
251		let assign_core_call =
252			RelayRuntimePallets::Coretime(AssignCore(core, begin, assignment, end_hint));
253
254		let message = Xcm(vec![
255			Instruction::UnpaidExecution {
256				weight_limit: WeightLimit::Unlimited,
257				check_origin: None,
258			},
259			Instruction::Transact {
260				origin_kind: OriginKind::Native,
261				call: assign_core_call.encode().into(),
262				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
263			},
264		]);
265
266		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
267			Ok(_) => log::info!(
268				target: "runtime::coretime",
269				"Core assignment sent successfully."
270			),
271			Err(e) => log::error!(
272				target: "runtime::coretime",
273				"Core assignment failed to send: {:?}",
274				e
275			),
276		}
277	}
278
279	fn on_new_timeslice(_t: pallet_broker::Timeslice) {
280		let stash = RevenueAccumulationAccount::get();
281		let value =
282			Balances::reducible_balance(&stash, Preservation::Expendable, Fortitude::Polite);
283
284		if value > 0 {
285			log::debug!(target: "runtime::coretime", "Going to burn {value} stashed tokens at RC");
286			match burn_at_relay(&stash, value) {
287				Ok(()) => {
288					log::debug!(target: "runtime::coretime", "Succesfully burnt {value} tokens");
289				},
290				Err(err) => {
291					log::error!(target: "runtime::coretime", "burn_at_relay failed: {err:?}");
292				},
293			}
294		}
295	}
296}
297
298pub struct SovereignAccountOf;
299impl MaybeConvert<TaskId, AccountId> for SovereignAccountOf {
300	fn maybe_convert(id: TaskId) -> Option<AccountId> {
301		// Currently all tasks are parachains.
302		let location = Location::new(1, [Parachain(id)]);
303		LocationToAccountId::convert_location(&location)
304	}
305}
306
307impl pallet_broker::Config for Runtime {
308	type RuntimeEvent = RuntimeEvent;
309	type Currency = Balances;
310	type OnRevenue = BurnCoretimeRevenue;
311	type TimeslicePeriod = ConstU32<{ coretime::TIMESLICE_PERIOD }>;
312	type MaxLeasedCores = ConstU32<50>;
313	type MaxReservedCores = ConstU32<10>;
314	type Coretime = CoretimeAllocator;
315	type ConvertBalance = sp_runtime::traits::Identity;
316	type WeightInfo = weights::pallet_broker::WeightInfo<Runtime>;
317	type PalletId = BrokerPalletId;
318	type AdminOrigin = EnsureRoot<AccountId>;
319	type SovereignAccountOf = SovereignAccountOf;
320	type MaxAutoRenewals = ConstU32<100>;
321	type PriceAdapter = pallet_broker::CenterTargetPrice<Balance>;
322}