referrerpolicy=no-referrer-when-downgrade

coretime_westend_runtime/
coretime.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
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, Timeslice,
32};
33use parachains_common::{AccountId, Balance};
34use sp_runtime::traits::{AccountIdConversion, MaybeConvert};
35use westend_runtime_constants::system_parachain::coretime;
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 = 66)]
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 const MinimumCreditPurchase: Balance = UNITS / 10;
115	pub RevenueAccumulationAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
116	pub const MinimumEndPrice: Balance = UNITS;
117}
118
119/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate
120/// from the parachain context. That is, the parachain provides a market (broker) for the sale of
121/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores.
122pub struct CoretimeAllocator;
123impl CoretimeInterface for CoretimeAllocator {
124	type AccountId = AccountId;
125	type Balance = Balance;
126	type RelayChainBlockNumberProvider = RelaychainDataProvider<Runtime>;
127
128	fn request_core_count(count: CoreIndex) {
129		use crate::coretime::CoretimeProviderCalls::RequestCoreCount;
130		let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count));
131
132		// Weight for `request_core_count` from westend benchmarks:
133		// `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000
134		// `proof_size` = 1636
135		// Add 5% to each component and round to 2 significant figures.
136		let call_weight = Weight::from_parts(190_000_000, 1700);
137
138		let message = Xcm(vec![
139			Instruction::UnpaidExecution {
140				weight_limit: WeightLimit::Unlimited,
141				check_origin: None,
142			},
143			Instruction::Transact {
144				origin_kind: OriginKind::Native,
145				call: request_core_count_call.encode().into(),
146				fallback_max_weight: Some(call_weight),
147			},
148		]);
149
150		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
151			Ok(_) => tracing::debug!(
152				target: "runtime::coretime",
153				"Request to update schedulable cores sent successfully."
154			),
155			Err(e) => tracing::error!(
156				target: "runtime::coretime", error=?e,
157				"Failed to send request to update schedulable cores"
158			),
159		}
160	}
161
162	fn request_revenue_info_at(when: RCBlockNumberOf<Self>) {
163		use crate::coretime::CoretimeProviderCalls::RequestRevenueInfoAt;
164		let request_revenue_info_at_call =
165			RelayRuntimePallets::Coretime(RequestRevenueInfoAt(when));
166
167		let message = Xcm(vec![
168			Instruction::UnpaidExecution {
169				weight_limit: WeightLimit::Unlimited,
170				check_origin: None,
171			},
172			Instruction::Transact {
173				origin_kind: OriginKind::Native,
174				call: request_revenue_info_at_call.encode().into(),
175				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
176			},
177		]);
178
179		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
180			Ok(_) => tracing::debug!(
181				target: "runtime::coretime",
182				"Request for revenue information sent successfully."
183			),
184			Err(e) => tracing::error!(
185				target: "runtime::coretime", error=?e,
186				"Request for revenue information failed to send"
187			),
188		}
189	}
190
191	fn credit_account(who: Self::AccountId, amount: Self::Balance) {
192		use crate::coretime::CoretimeProviderCalls::CreditAccount;
193		let credit_account_call = RelayRuntimePallets::Coretime(CreditAccount(who, amount));
194
195		let message = Xcm(vec![
196			Instruction::UnpaidExecution {
197				weight_limit: WeightLimit::Unlimited,
198				check_origin: None,
199			},
200			Instruction::Transact {
201				origin_kind: OriginKind::Native,
202				call: credit_account_call.encode().into(),
203				fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)),
204			},
205		]);
206
207		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
208			Ok(_) => tracing::debug!(
209				target: "runtime::coretime",
210				"Instruction to credit account sent successfully."
211			),
212			Err(e) => tracing::error!(
213				target: "runtime::coretime", error=?e,
214				"Instruction to credit account failed to send"
215			),
216		}
217	}
218
219	fn assign_core(
220		core: CoreIndex,
221		begin: RCBlockNumberOf<Self>,
222		assignment: Vec<(CoreAssignment, PartsOf57600)>,
223		end_hint: Option<RCBlockNumberOf<Self>>,
224	) {
225		use crate::coretime::CoretimeProviderCalls::AssignCore;
226
227		// Weight for `assign_core` from westend benchmarks:
228		// `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315
229		// `proof_size` = 3612
230		// Add 5% to each component and round to 2 significant figures.
231		let call_weight = Weight::from_parts(980_000_000, 3800);
232
233		// The relay chain currently only allows `assign_core` to be called with a complete mask
234		// and only ever with increasing `begin`. The assignments must be truncated to avoid
235		// dropping that core's assignment completely.
236
237		// This shadowing of `assignment` is temporary and can be removed when the relay can accept
238		// multiple messages to assign a single core.
239		let assignment = if assignment.len() > 28 {
240			let mut total_parts = 0u16;
241			// Account for missing parts with a new `Idle` assignment at the start as
242			// `assign_core` on the relay assumes this is sorted. We'll add the rest of the
243			// assignments and sum the parts in one pass, so this is just initialized to 0.
244			let mut assignment_truncated = vec![(CoreAssignment::Idle, 0)];
245			// Truncate to first 27 non-idle assignments.
246			assignment_truncated.extend(
247				assignment
248					.into_iter()
249					.filter(|(a, _)| *a != CoreAssignment::Idle)
250					.take(27)
251					.inspect(|(_, parts)| total_parts += *parts)
252					.collect::<Vec<_>>(),
253			);
254
255			// Set the parts of the `Idle` assignment we injected at the start of the vec above.
256			assignment_truncated[0].1 = 57_600u16.saturating_sub(total_parts);
257			assignment_truncated
258		} else {
259			assignment
260		};
261
262		let assign_core_call =
263			RelayRuntimePallets::Coretime(AssignCore(core, begin, assignment, end_hint));
264
265		let message = Xcm(vec![
266			Instruction::UnpaidExecution {
267				weight_limit: WeightLimit::Unlimited,
268				check_origin: None,
269			},
270			Instruction::Transact {
271				origin_kind: OriginKind::Native,
272				call: assign_core_call.encode().into(),
273				fallback_max_weight: Some(call_weight),
274			},
275		]);
276
277		match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) {
278			Ok(_) => tracing::debug!(
279				target: "runtime::coretime",
280				"Core assignment sent successfully."
281			),
282			Err(e) => tracing::error!(
283				target: "runtime::coretime", error=?e,
284				"Core assignment failed to send"
285			),
286		}
287	}
288
289	fn on_new_timeslice(_timeslice: Timeslice) {
290		let stash = RevenueAccumulationAccount::get();
291		let value =
292			Balances::reducible_balance(&stash, Preservation::Expendable, Fortitude::Polite);
293
294		if value > 0 {
295			tracing::debug!(target: "runtime::coretime", %value, "Going to burn stashed tokens at RC");
296			match burn_at_relay(&stash, value) {
297				Ok(()) => {
298					tracing::debug!(target: "runtime::coretime", %value, "Successfully burnt tokens");
299				},
300				Err(err) => {
301					tracing::error!(target: "runtime::coretime", error=?err, "burn_at_relay failed");
302				},
303			}
304		}
305	}
306}
307
308pub struct SovereignAccountOf;
309impl MaybeConvert<TaskId, AccountId> for SovereignAccountOf {
310	fn maybe_convert(id: TaskId) -> Option<AccountId> {
311		// Currently all tasks are parachains.
312		let location = Location::new(1, [Parachain(id)]);
313		LocationToAccountId::convert_location(&location)
314	}
315}
316
317impl pallet_broker::Config for Runtime {
318	type RuntimeEvent = RuntimeEvent;
319	type Currency = Balances;
320	type OnRevenue = BurnCoretimeRevenue;
321	type TimeslicePeriod = ConstU32<{ coretime::TIMESLICE_PERIOD }>;
322	// We don't actually need any leases at launch but set to 10 in case we want to sudo some in.
323	type MaxLeasedCores = ConstU32<10>;
324	type MaxReservedCores = ConstU32<10>;
325	type Coretime = CoretimeAllocator;
326	type ConvertBalance = sp_runtime::traits::Identity;
327	type WeightInfo = weights::pallet_broker::WeightInfo<Runtime>;
328	type PalletId = BrokerPalletId;
329	type AdminOrigin = EnsureRoot<AccountId>;
330	type SovereignAccountOf = SovereignAccountOf;
331	type MaxAutoRenewals = ConstU32<20>;
332	type PriceAdapter = pallet_broker::MinimumPrice<Balance, MinimumEndPrice>;
333	type MinimumCreditPurchase = MinimumCreditPurchase;
334}