referrerpolicy=no-referrer-when-downgrade

pallet_registrar_para/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Parachain registrar pallet
19//!
20//! The user-facing half of parachain registration. It hands out para ids, takes the manager's
21//! deposits as [`Consideration`] tickets, and coordinates the registration itself asynchronously
22//! with the chain that owns the parachain registry.
23//!
24//! Both directions of that coordination are abstract: requests go out through [`SendToRelay`],
25//! verdicts come back in through [`Pallet::receive`], gated by [`Config::RelayOrigin`]. Nothing
26//! here depends on XCM or on the other chain's extrinsics.
27//!
28//! ## Registration flow
29//!
30//! Registration takes two transactions on two chains. Sending a multi-megabyte validation code
31//! through the messaging layer would be wasteful, so this chain only commits to its hash and
32//! length and the blob is uploaded to the relay chain directly:
33//!
34//! 1. [`Pallet::reserve`] allocates a para id here and takes [`Config::ReservationConsideration`].
35//! 2. [`Pallet::register`] takes [`Config::RegistrationConsideration`] for the head data and the
36//!    *declared* code length, then asks the relay chain to accept the registration. Only the code
37//!    hash and length are sent.
38//! 3. The manager uploads the validation code to the relay chain, which accepts it only if it
39//!    matches the hash and length committed to in step 2.
40//! 4. The verdict arrives back as [`Pallet::receive`], which either finalises the registration or
41//!    releases the registration deposit.
42//!
43//! ## Giving up
44//!
45//! Nothing on the relay chain times a registration out, so a request whose code never turns up
46//! waits until the manager ends it with [`Pallet::cancel_registration`]. That asks the relay chain
47//! to drop the authorization and only releases the deposit once it confirms, which is what
48//! keeps a cancellation from freeing the deposit on a para that did register after all.
49//!
50//! Deposits only ever live on this chain; the relay chain takes nothing.
51
52#![cfg_attr(not(feature = "std"), no_std)]
53
54extern crate alloc;
55
56use alloc::vec::Vec;
57use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
58use frame_support::{
59	defensive,
60	traits::{Consideration, Footprint},
61};
62use registrar_primitives::{
63	FailureReason, MessageToPara, MessageToParaV1, MessageToRelay, MessageToRelayV1, Outcome,
64	ParaId,
65};
66use scale_info::TypeInfo;
67use sp_core::H256;
68use sp_runtime::{
69	traits::{BlockNumberProvider, Saturating},
70	DispatchResult,
71};
72
73pub use pallet::*;
74pub use weights::WeightInfo;
75
76pub mod weights;
77
78#[cfg(feature = "runtime-benchmarks")]
79mod benchmarking;
80#[cfg(test)]
81mod mock;
82#[cfg(test)]
83mod tests;
84
85/// Block number used for registration deadlines.
86///
87/// On a parachain, configure [`Config::BlockNumberProvider`] to
88/// `cumulus_pallet_parachain_system::RelaychainDataProvider`, so deadlines are expressed in
89/// relay-chain blocks and keep their meaning through a stall in this chain's own block production.
90pub type ProvidedBlockNumberOf<T> =
91	<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
92
93/// Used to send an XCM `Transact` to the registrar pallet on the remote relay chain.
94pub trait SendToRelay {
95	/// The account id used to identify a registration's manager on both chains.
96	type AccountId;
97
98	/// Send `message` to the relay chain.
99	///
100	/// `Err(())` means the message could not be handed to the transport at all. Callers are
101	/// expected to fail the whole extrinsic, so nothing is left half-done.
102	#[allow(clippy::result_unit_err)]
103	fn send(message: MessageToRelay<Self::AccountId>) -> Result<(), ()>;
104}
105
106#[cfg(feature = "std")]
107impl SendToRelay for () {
108	type AccountId = sp_runtime::AccountId32;
109
110	fn send(_message: MessageToRelay<Self::AccountId>) -> Result<(), ()> {
111		Ok(())
112	}
113}
114
115/// Where a para id sits in the registration flow.
116#[derive(
117	Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen,
118)]
119pub enum RegistrationState<Ticket, BlockNumber> {
120	/// The para id is held by its manager, but nothing is registered on the relay chain yet.
121	Reserved,
122	/// The relay chain has been asked to register this para and has not reported back.
123	Pending {
124		/// The registration's [`Consideration`] ticket, returned if the registration fails.
125		ticket: Ticket,
126		/// The block from which the manager may give up on this registration.
127		///
128		/// Expressed in [`Config::BlockNumberProvider`] blocks. Long enough that a verdict already
129		/// on its way arrives first, so a cancellation is only ever sent for a registration that
130		/// really has gone quiet. Pushed out again by every [`Pallet::cancel_registration`], so a
131		/// cancellation that gets lost can be retried but not spammed.
132		cancellable_at: BlockNumber,
133	},
134	/// The relay chain has onboarded this para.
135	Registered {
136		/// The registration's [`Consideration`] ticket, kept while the para is registered.
137		ticket: Ticket,
138	},
139}
140
141/// Everything this chain knows about one para id.
142#[derive(
143	Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo, MaxEncodedLen, Debug,
144)]
145pub struct ParaInfo<AccountId, ReservationTicket, RegistrationTicket, BlockNumber> {
146	/// The account that reserved the para id and controls it.
147	pub manager: AccountId,
148	/// The [`Consideration`] ticket for the para id itself.
149	pub reservation: ReservationTicket,
150	/// Where this para id sits in the registration flow.
151	pub state: RegistrationState<RegistrationTicket, BlockNumber>,
152}
153
154/// The [`ParaInfo`] type as configured.
155pub type ParaInfoOf<T> = ParaInfo<
156	<T as frame_system::Config>::AccountId,
157	<T as Config>::ReservationConsideration,
158	<T as Config>::RegistrationConsideration,
159	ProvidedBlockNumberOf<T>,
160>;
161
162#[frame_support::pallet]
163pub mod pallet {
164	use super::*;
165	use frame_support::pallet_prelude::{DispatchResult, *};
166	use frame_system::pallet_prelude::*;
167
168	#[pallet::config]
169	pub trait Config: frame_system::Config {
170		/// The cost of reserving a para id. The footprint is a single zero-sized item, so a flat
171		/// price fits.
172		type ReservationConsideration: Consideration<Self::AccountId, Footprint>;
173
174		/// The cost of a registration, on top of the reservation. The footprint is one item sized
175		/// as head data plus *declared* code length, so a per-byte price fits.
176		type RegistrationConsideration: Consideration<Self::AccountId, Footprint>;
177
178		/// Sends messages to the relay chain.
179		type SendToRelay: SendToRelay<AccountId = Self::AccountId>;
180
181		/// An origin that is sure to be the relay chain's registrar pallet.
182		type RelayOrigin: EnsureOrigin<Self::RuntimeOrigin>;
183
184		/// The lowest para id this pallet will hand out.
185		///
186		/// Mirrors the relay chain's `LOWEST_PUBLIC_ID`. Ids below it are reserved for system
187		/// parachains and are not obtainable here.
188		#[pallet::constant]
189		type FirstPublicParaId: Get<ParaId>;
190
191		/// The smallest validation code the relay chain will accept.
192		///
193		/// A local mirror of the relay chain's `MIN_CODE_SIZE`, used to fail early. The relay
194		/// chain checks the real thing against its own live configuration.
195		#[pallet::constant]
196		type MinCodeSize: Get<u32>;
197
198		/// The largest validation code the relay chain will accept.
199		///
200		/// A local mirror of the relay chain's `max_code_size`. See [`Config::MinCodeSize`].
201		#[pallet::constant]
202		type MaxCodeSize: Get<u32>;
203
204		/// The largest head data the relay chain will accept.
205		///
206		/// A local mirror of the relay chain's `max_head_data_size`. See [`Config::MinCodeSize`].
207		#[pallet::constant]
208		type MaxHeadDataSize: Get<u32>;
209
210		/// How long a manager waits for the relay chain before giving up on a registration.
211		///
212		/// Measured in [`Config::BlockNumberProvider`] blocks. Should comfortably cover a round
213		/// trip, so that a verdict that is merely slow lands before anybody tries to cancel.
214		#[pallet::constant]
215		type PendingDeadline: Get<ProvidedBlockNumberOf<Self>>;
216
217		/// Source of block numbers for registration deadlines.
218		///
219		/// On a parachain this should be
220		/// `cumulus_pallet_parachain_system::RelaychainDataProvider`, so
221		/// [`Config::PendingDeadline`] is in relay-chain blocks.
222		type BlockNumberProvider: BlockNumberProvider;
223
224		/// Weight information for the extrinsics in this pallet.
225		type WeightInfo: WeightInfo;
226	}
227
228	#[pallet::pallet]
229	pub struct Pallet<T>(_);
230
231	/// Hold reasons for runtimes that pay the considerations out of held funds.
232	#[pallet::composite_enum]
233	pub enum HoldReason {
234		/// Held for keeping a para id reserved.
235		#[codec(index = 0)]
236		ParaIdReservation,
237		/// Held for the head data and validation code of a registration.
238		#[codec(index = 1)]
239		Registration,
240	}
241
242	/// The next para id that [`Pallet::reserve`] will hand out.
243	#[pallet::storage]
244	pub type NextFreeParaId<T: Config> = StorageValue<_, ParaId, ValueQuery>;
245
246	/// The id the next message to the relay chain will carry.
247	///
248	/// One per message sent, echoed back in the relay chain's response, so a request, its
249	/// response and the events on both chains can be tied together.
250	#[pallet::storage]
251	pub type NextMessageId<T: Config> = StorageValue<_, u64, ValueQuery>;
252
253	/// Every para id reserved through this pallet, and what is happening with it.
254	#[pallet::storage]
255	pub type Paras<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, ParaInfoOf<T>>;
256
257	#[pallet::event]
258	#[pallet::generate_deposit(pub(super) fn deposit_event)]
259	pub enum Event<T: Config> {
260		/// A para id was reserved.
261		Reserved { para_id: ParaId, who: T::AccountId },
262		/// A registration was requested and the relay chain has been asked to accept it.
263		RegisterRequested { para_id: ParaId, message_id: u64, manager: T::AccountId },
264		/// The relay chain confirmed a registration.
265		Registered { para_id: ParaId, message_id: u64, manager: T::AccountId },
266		/// The relay chain rejected a registration. The registration consideration was returned.
267		RegistrationFailed {
268			para_id: ParaId,
269			message_id: u64,
270			manager: T::AccountId,
271			reason: FailureReason,
272		},
273		/// A manager gave up on a pending registration, and the relay chain has been asked to
274		/// drop the authorization. The consideration stays taken until it answers.
275		CancelRequested { para_id: ParaId, message_id: u64, manager: T::AccountId },
276		/// The relay chain confirmed a cancellation. The registration consideration was returned.
277		RegistrationCancelled { para_id: ParaId, message_id: u64, manager: T::AccountId },
278	}
279
280	#[pallet::error]
281	pub enum Error<T> {
282		/// The para id has not been reserved.
283		NotReserved,
284		/// The caller does not manage this para id.
285		NotOwner,
286		/// The para id is already registered, or a registration is already in flight for it.
287		AlreadyRegistered,
288		/// There is no registration in flight for this para id.
289		NotPending,
290		/// The manager may not abandon this registration yet.
291		CannotCancelYet,
292		/// The head data is larger than the relay chain will accept.
293		HeadDataTooLarge,
294		/// The validation code is larger than the relay chain will accept.
295		CodeTooLarge,
296		/// The validation code is smaller than the relay chain will accept.
297		CodeTooSmall,
298		/// The message could not be handed to the transport.
299		SendFailed,
300		/// There are no more para ids to hand out.
301		NoFreeParaId,
302	}
303
304	#[pallet::hooks]
305	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
306		fn integrity_test() {
307			// Otherwise no validation code could ever pass `register`.
308			assert!(
309				T::MinCodeSize::get() <= T::MaxCodeSize::get(),
310				"MinCodeSize ({}) must not exceed MaxCodeSize ({})",
311				T::MinCodeSize::get(),
312				T::MaxCodeSize::get(),
313			);
314		}
315	}
316
317	#[pallet::call]
318	impl<T: Config> Pallet<T> {
319		/// Reserve the next free para id for the caller.
320		///
321		/// Takes [`Config::ReservationConsideration`]. The caller becomes the manager of the new
322		/// id and is the only account that may [`Pallet::register`] against it.
323		#[pallet::call_index(0)]
324		#[pallet::weight(T::WeightInfo::reserve())]
325		pub fn reserve(origin: OriginFor<T>) -> DispatchResult {
326			let who = ensure_signed(origin)?;
327
328			let para_id = NextFreeParaId::<T>::get().max(T::FirstPublicParaId::get());
329			let next = para_id.checked_add(1).ok_or(Error::<T>::NoFreeParaId)?;
330			ensure!(!Paras::<T>::contains_key(para_id), Error::<T>::AlreadyRegistered);
331
332			let reservation = T::ReservationConsideration::new(&who, Footprint::from_parts(1, 0))?;
333
334			Paras::<T>::insert(
335				para_id,
336				ParaInfo { manager: who.clone(), reservation, state: RegistrationState::Reserved },
337			);
338			NextFreeParaId::<T>::put(next);
339
340			Self::deposit_event(Event::Reserved { para_id, who });
341			Ok(())
342		}
343
344		/// Ask the relay chain to register head data and validation code for a reserved para id.
345		///
346		/// The validation code itself stays here: only `code_hash` and `code_len` are sent. The
347		/// caller uploads the blob to the relay chain separately, which accepts it only if it
348		/// hashes to `code_hash` and is exactly `code_len` bytes long.
349		///
350		/// ## Costs
351		///
352		/// Takes [`Config::RegistrationConsideration`] for the head data and the *declared* code
353		/// length, on top of the para id reservation. It is returned if the relay chain rejects
354		/// the registration or if the caller later abandons it.
355		#[pallet::call_index(1)]
356		#[pallet::weight(T::WeightInfo::register(genesis_head.len() as u32))]
357		pub fn register(
358			origin: OriginFor<T>,
359			para_id: ParaId,
360			genesis_head: Vec<u8>,
361			code_len: u32,
362			code_hash: H256,
363		) -> DispatchResult {
364			let who = ensure_signed(origin)?;
365
366			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
367			ensure!(info.manager == who, Error::<T>::NotOwner);
368			ensure!(
369				matches!(info.state, RegistrationState::Reserved),
370				Error::<T>::AlreadyRegistered
371			);
372
373			let head_len = genesis_head.len() as u32;
374			ensure!(head_len <= T::MaxHeadDataSize::get(), Error::<T>::HeadDataTooLarge);
375			ensure!(code_len >= T::MinCodeSize::get(), Error::<T>::CodeTooSmall);
376			ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
377
378			let ticket = T::RegistrationConsideration::new(
379				&who,
380				Self::registration_footprint(head_len, code_len),
381			)?;
382
383			let cancellable_at = T::BlockNumberProvider::current_block_number()
384				.saturating_add(T::PendingDeadline::get());
385			info.state = RegistrationState::Pending { ticket, cancellable_at };
386			Paras::<T>::insert(para_id, info);
387
388			// A transport failure returns `Err` and unwinds everything above, ticket included.
389			let message_id = Self::next_message_id();
390			T::SendToRelay::send(MessageToRelay::V1(MessageToRelayV1::Register {
391				para_id,
392				message_id,
393				manager: who.clone(),
394				genesis_head,
395				code_hash,
396				code_len,
397			}))
398			.map_err(|()| Error::<T>::SendFailed)?;
399
400			Self::deposit_event(Event::RegisterRequested { para_id, message_id, manager: who });
401			Ok(())
402		}
403
404		/// Give up on a registration the relay chain never reported on.
405		///
406		/// Callable from [`Config::PendingDeadline`] blocks after the request. Nothing on the
407		/// relay chain abandons a registration on its own, so this is what ends one whose code
408		/// never turned up, and the manager pays for the round trip rather than every relay-chain
409		/// block paying for a sweep.
410		///
411		/// The deposit is not released here: the relay chain is asked to drop the authorization
412		/// first, and [`Pallet::receive`] releases the deposit when it confirms. Waiting for that
413		/// answer is the point. A registration that did go through, with a verdict that got lost on
414		/// the way here, must not have its deposit refunded, and only the relay chain knows
415		/// which of the two happened.
416		///
417		/// The para id itself stays reserved either way, so the manager can simply try again.
418		#[pallet::call_index(2)]
419		#[pallet::weight(T::WeightInfo::cancel_registration())]
420		pub fn cancel_registration(origin: OriginFor<T>, para_id: ParaId) -> DispatchResult {
421			let who = ensure_signed(origin)?;
422
423			let mut info = Paras::<T>::get(para_id).ok_or(Error::<T>::NotReserved)?;
424			ensure!(info.manager == who, Error::<T>::NotOwner);
425			let RegistrationState::Pending { ticket, cancellable_at } = info.state else {
426				return Err(Error::<T>::NotPending.into());
427			};
428			let now = T::BlockNumberProvider::current_block_number();
429			ensure!(now >= cancellable_at, Error::<T>::CannotCancelYet);
430
431			// Another deadline's grace before the manager may ask again, so a request that goes
432			// missing can be retried without the relay chain being asked once per block.
433			info.state = RegistrationState::Pending {
434				ticket,
435				cancellable_at: now.saturating_add(T::PendingDeadline::get()),
436			};
437			Paras::<T>::insert(para_id, info);
438
439			// A transport failure returns `Err` and unwinds the new deadline with it.
440			let message_id = Self::next_message_id();
441			T::SendToRelay::send(MessageToRelay::V1(MessageToRelayV1::CancelRegistration {
442				para_id,
443				message_id,
444			}))
445			.map_err(|()| Error::<T>::SendFailed)?;
446
447			Self::deposit_event(Event::CancelRequested { para_id, message_id, manager: who });
448			Ok(())
449		}
450
451		/// Accept a report from the relay chain's registrar pallet.
452		///
453		/// Not callable by users: the origin must be the relay chain.
454		#[pallet::call_index(3)]
455		#[pallet::weight(T::WeightInfo::receive())]
456		pub fn receive(origin: OriginFor<T>, message: MessageToPara) -> DispatchResult {
457			T::RelayOrigin::ensure_origin_or_root(origin)?;
458
459			match message {
460				MessageToPara::V1(MessageToParaV1::RegisterResponse {
461					para_id,
462					message_id,
463					outcome,
464				}) => Self::on_register_response(para_id, message_id, outcome),
465				MessageToPara::V1(MessageToParaV1::CancelResponse {
466					para_id,
467					message_id,
468					outcome,
469				}) => Self::on_cancel_response(para_id, message_id, outcome),
470			}
471		}
472	}
473}
474
475impl<T: Config> Pallet<T> {
476	/// The footprint a registration is charged for: the head data plus the *declared* code length.
477	pub fn registration_footprint(head_len: u32, code_len: u32) -> Footprint {
478		Footprint::from_parts(1, head_len.saturating_add(code_len) as usize)
479	}
480
481	/// Take the id for the next message to the relay chain.
482	fn next_message_id() -> u64 {
483		NextMessageId::<T>::mutate(|next| {
484			let id = *next;
485			*next = next.wrapping_add(1);
486			id
487		})
488	}
489
490	/// Apply the relay chain's verdict on a registration.
491	///
492	/// A response about a para id we are not expecting one for is dropped rather than treated as a
493	/// dispatch error: erroring here would unwind the whole incoming message for something we can
494	/// do nothing about anyway. Unexpected responses still trip a defensive failure so they are
495	/// loud in logs (and panic under `debug_assertions`).
496	fn on_register_response(para_id: ParaId, message_id: u64, outcome: Outcome) -> DispatchResult {
497		let Some(mut info) = Paras::<T>::get(para_id) else {
498			defensive!("register response for unknown para, dropping", para_id);
499			return Ok(());
500		};
501		let RegistrationState::Pending { ticket, .. } = info.state else {
502			defensive!("register response for para which is not pending, dropping", para_id);
503			return Ok(());
504		};
505
506		let manager = info.manager.clone();
507		match outcome {
508			Ok(()) => {
509				info.state = RegistrationState::Registered { ticket };
510				Paras::<T>::insert(para_id, info);
511				Self::deposit_event(Event::Registered { para_id, message_id, manager });
512			},
513			Err(reason) => {
514				ticket.drop(&info.manager)?;
515				info.state = RegistrationState::Reserved;
516				Paras::<T>::insert(para_id, info);
517				Self::deposit_event(Event::RegistrationFailed {
518					para_id,
519					message_id,
520					manager,
521					reason,
522				});
523			},
524		}
525
526		Ok(())
527	}
528
529	/// Apply the relay chain's answer to a cancellation.
530	///
531	/// `Ok(())` means the authorization is gone, so the deposit goes back. The one refusal is
532	/// [`FailureReason::AlreadyRegistered`]: the code landed after all and the earlier verdict was
533	/// simply lost, so the para is recorded as registered and the deposit stays held.
534	///
535	/// Unlike a register response, an answer for a para that is no longer pending is expected
536	/// rather than defensive: a verdict already in flight when the cancellation was sent settles
537	/// the registration first, and this then has nothing left to do.
538	fn on_cancel_response(para_id: ParaId, message_id: u64, outcome: Outcome) -> DispatchResult {
539		let Some(mut info) = Paras::<T>::get(para_id) else {
540			defensive!("cancel response for unknown para, dropping", para_id);
541			return Ok(());
542		};
543		let RegistrationState::Pending { ticket, .. } = info.state else {
544			log::debug!(
545				target: "runtime::registrar-para",
546				"cancel response for para {para_id} which is no longer pending, dropping",
547			);
548			return Ok(());
549		};
550
551		let manager = info.manager.clone();
552		match outcome {
553			Ok(()) => {
554				ticket.drop(&info.manager)?;
555				info.state = RegistrationState::Reserved;
556				Paras::<T>::insert(para_id, info);
557				Self::deposit_event(Event::RegistrationCancelled { para_id, message_id, manager });
558			},
559			Err(FailureReason::AlreadyRegistered) => {
560				info.state = RegistrationState::Registered { ticket };
561				Paras::<T>::insert(para_id, info);
562				Self::deposit_event(Event::Registered { para_id, message_id, manager });
563			},
564			// Nothing else is a cancellation the relay chain refuses, so leave the registration
565			// pending: the manager can ask again once the deadline comes round.
566			Err(reason) => {
567				defensive!("unexpected cancel refusal, leaving pending", (para_id, &reason));
568			},
569		}
570
571		Ok(())
572	}
573}